From 9d0d5103486e4116ccde71b9139978cb78acd469 Mon Sep 17 00:00:00 2001 From: zhengchao Date: Sun, 3 May 2020 17:19:48 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=96=B0=E5=A2=9EIP=E5=9B=9E=E8=B0=83?= =?UTF-8?q?=E8=A1=A8=EF=BC=8C=E5=8F=AF=E4=BB=A5=E8=BF=9B=E8=A1=8CIPv4?= =?UTF-8?q?=E5=92=8CIPv6=E7=9A=84=E5=8C=BA=E9=97=B4=E5=8C=B9=E9=85=8D?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- inc/Maat_rule.h | 15 +- src/CMakeLists.txt | 2 + src/entry/Maat_api.cpp | 21 +++ src/entry/Maat_rule.cpp | 230 ++++++++++--------------- src/entry/Maat_table.cpp | 118 ++++++++++++- src/entry/Maat_table_runtime.cpp | 179 ++++++++++++++++--- src/entry/Maat_utils.cpp | 129 ++++++++++++++ src/inc_internal/Maat_rule_internal.h | 7 +- src/inc_internal/Maat_table.h | 18 +- src/inc_internal/Maat_table_runtime.h | 12 ++ src/inc_internal/Maat_utils.h | 9 + src/inc_internal/view_only/IPMatcher.h | 117 +++++++++++++ vendor/CMakeLists.txt | 15 ++ vendor/IPMatcher-20200503.zip | Bin 0 -> 86786 bytes 14 files changed, 694 insertions(+), 178 deletions(-) create mode 100644 src/inc_internal/view_only/IPMatcher.h create mode 100644 vendor/IPMatcher-20200503.zip diff --git a/inc/Maat_rule.h b/inc/Maat_rule.h index 124c9ad..ca557f3 100644 --- a/inc/Maat_rule.h +++ b/inc/Maat_rule.h @@ -293,14 +293,27 @@ typedef void Maat_plugin_EX_free_func_t(int table_id, MAAT_PLUGIN_EX_DATA* ad, l typedef void Maat_plugin_EX_dup_func_t(int table_id, MAAT_PLUGIN_EX_DATA *to, MAAT_PLUGIN_EX_DATA *from, long argl, void *argp); typedef int Maat_plugin_EX_key2index_func_t(const char* key); +//For IP plugin, key2index_func MUST be NULL. int Maat_plugin_EX_register(Maat_feather_t feather, int table_id, Maat_plugin_EX_new_func_t* new_func, Maat_plugin_EX_free_func_t* free_func, Maat_plugin_EX_dup_func_t* dup_func, Maat_plugin_EX_key2index_func_t* key2index_func, long argl, void *argp); -//Data is duplicated by dup_func of Maat_plugin_EX_register, caller is responsible to free the data. +//Data is duplicated by dup_func of Maat_plugin_EX_register, caller is responsible to FREE the data. MAAT_PLUGIN_EX_DATA Maat_plugin_get_EX_data(Maat_feather_t feather, int table_id, const char* key); +struct ip_address +{ + int ip_type; //4: IPv4, 6: IPv6 + union + { + unsigned int ipv4; //network order + unsigned int ipv6[4]; + }; +}; + +MAAT_PLUGIN_EX_DATA Maat_IP_plugin_get_EX_data(Maat_feather_t feather, int table_id, const struct ip_address* ip); + enum MAAT_RULE_OPT { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f19b34e..db42f65 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -21,6 +21,7 @@ target_include_directories(maat_frame_static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} #target_include_directories(maat_frame_static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/inc_internal/hiredis) target_link_libraries(maat_frame_static hiredis-vip-static) target_link_libraries(maat_frame_static igraph-static) +target_link_libraries(maat_frame_static ipmatcher-static) # Shared Library Output add_library(maat_frame_shared SHARED ${MAAT_SRC}) @@ -34,6 +35,7 @@ target_include_directories(maat_frame_shared PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} target_include_directories(maat_frame_shared PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/inc_internal/hiredis) target_link_libraries(maat_frame_shared hiredis-vip-static ${MAAT_DEPEND_DYN_LIB}) target_link_libraries(maat_frame_shared igraph-static ${MAAT_DEPEND_DYN_LIB}) +target_link_libraries(maat_frame_shared ipmatcher-static) install(FILES ${PROJECT_SOURCE_DIR}/inc/Maat_rule.h DESTINATION /opt/MESA/include/MESA/ COMPONENT HEADER) install(FILES ${PROJECT_SOURCE_DIR}/inc/Maat_command.h DESTINATION /opt/MESA/include/MESA/ COMPONENT HEADER) diff --git a/src/entry/Maat_api.cpp b/src/entry/Maat_api.cpp index c8122a6..cd031a2 100644 --- a/src/entry/Maat_api.cpp +++ b/src/entry/Maat_api.cpp @@ -1380,6 +1380,27 @@ MAAT_PLUGIN_EX_DATA Maat_plugin_get_EX_data(Maat_feather_t feather, int table_id exdata=Maat_table_runtime_plugin_get_ex_data(table_rt, table_desc, key); return exdata; } +int Maat_IP_plugin_get_EX_data(Maat_feather_t feather, int table_id, const struct ip_address* ip, MAAT_PLUGIN_EX_DATA* ex_data_array, size_t n_ex_data) +{ + struct _Maat_feather_t* _feather=(_Maat_feather_t*)feather; + struct Maat_table_schema *table_desc=NULL; + struct Maat_table_runtime *table_rt=NULL; + int n_get=0; + if(_feather->scanner==NULL) + { + return 0; + } + table_desc=Maat_table_get_scan_by_id(_feather->table_mgr, table_id, TABLE_TYPE_PLUGIN, NULL); + table_rt=Maat_table_runtime_get(_feather->scanner->table_rt_mgr, table_id); + if(table_rt->table_type!=TABLE_TYPE_IP_PLUGIN) + { + return -1; + } + + n_get=Maat_table_runtime_ip_plugin_get_N_ex_data(table_rt, table_desc, ip, ex_data_array, n_ex_data); + return n_get; + +} int Maat_full_scan_string_detail(Maat_feather_t feather,int table_id ,enum MAAT_CHARSET charset,const char* data,int data_len diff --git a/src/entry/Maat_rule.cpp b/src/entry/Maat_rule.cpp index b6aaca5..34c2eed 100644 --- a/src/entry/Maat_rule.cpp +++ b/src/entry/Maat_rule.cpp @@ -2410,142 +2410,7 @@ error_out: free(maat_str_rule); maat_str_rule=NULL; } -enum MAAT_IP_FORMAT -{ - FORMAT_RANGE, - FORMAT_MASK, - FORMAT_CIDR, - FORMAT_UNKNOWN -}; -enum MAAT_IP_FORMAT ip_format_str2int(const char* format) -{ - if(0==strcasecmp(format, "range")) - { - return FORMAT_RANGE; - } - else if(0==strcasecmp(format, "mask")) - { - return FORMAT_MASK; - } - else if(0==strcasecmp(format, "CIDR")) - { - return FORMAT_CIDR; - } - else - { - assert(0); - } - return FORMAT_UNKNOWN; -} -int ip_format2range(int ip_type, enum MAAT_IP_FORMAT format, const char* ip1, const char* ip2, unsigned int range_begin[], unsigned int range_end[]) -{ - unsigned int ipv4_addr=0, ipv4_mask=0, ipv4_range_end=0; - unsigned int ipv6_addr[4]={0}, ipv6_mask[4]={0}, ipv6_range_end[4]={0}; - int cidr=0, bit32=0; - int ret=0, i=0; - if(ip_type!=4 && ip_type!=6) - { - assert(0); - return -1; - } - if(ip_type==4) - { - ret=inet_pton(AF_INET, ip1, &ipv4_addr); - if(ret<=0) - { - return -1; - } - ipv4_addr=ntohl(ipv4_addr); - switch (format) - { - case FORMAT_RANGE: - range_begin[0]=ipv4_addr; - ret=inet_pton(AF_INET, ip2, &ipv4_range_end); - if(ret<=0) - { - return -1; - } - ipv4_range_end=ntohl(ipv4_range_end); - range_end[0]=ipv4_range_end; - break; - case FORMAT_MASK: - ret=inet_pton(AF_INET, ip2, &ipv4_mask); - if(ret<=0) - { - return -1; - } - ipv4_mask=ntohl(ipv4_mask); - range_begin[0]=ipv4_addr&ipv4_mask; - range_end[0]=ipv4_addr|~ipv4_mask; - break; - case FORMAT_CIDR: - cidr=atoi(ip2); - if(cidr>32||cidr<0) - { - return -1; - } - ipv4_mask = (0xFFFFFFFFUL << (32 - cidr)) & 0xFFFFFFFFUL; - range_begin[0]=ipv4_addr&ipv4_mask; - range_end[0]=ipv4_addr|~ipv4_mask; - break; - default: - assert(0); - } - } - else //ipv6 - { - ret=inet_pton(AF_INET6, ip1, ipv6_addr); - if(ret<=0) - { - return -1; - } - ipv6_ntoh(ipv6_addr); - switch(format) - { - case FORMAT_RANGE: - ret=inet_pton(AF_INET6, ip2, ipv6_range_end); - if(ret<=0) - { - return -1; - } - ipv6_ntoh(ipv6_range_end); - memcpy(range_begin, ipv6_addr, sizeof(ipv6_addr)); - memcpy(range_end, ipv6_range_end, sizeof(ipv6_range_end)); - break; - case FORMAT_MASK: - ret=inet_pton(AF_INET6, ip2, ipv6_mask); - if(ret<=0) - { - return -1; - } - ipv6_ntoh(ipv6_mask); - for(i=0; i<4; i++) - { - range_begin[i]=ipv6_addr[i]&ipv6_mask[i]; - range_end[i] = ipv6_addr[i]|~ipv6_mask[i]; - } - break; - case FORMAT_CIDR: - cidr=atoi(ip2); - if(cidr>128||cidr<0) - { - return -1; - } - for(i=0; i<4; i++) - { - bit32=128-cidr-32*(3-i); - if(bit32<0) bit32=0; - ipv6_mask[i]=(0xFFFFFFFFUL << bit32) & 0xFFFFFFFFUL; - range_begin[i]=ipv6_addr[i]&ipv6_mask[i]; - range_end[i] = ipv6_addr[i]|~ipv6_mask[i]; - } - break; - default: - assert(0); - } - } - return 0; -} + void update_ip_rule(struct Maat_table_schema* table, const char* table_line, struct Maat_scanner *scanner, void* logger) { struct db_ip_rule_t* ip_rule=(struct db_ip_rule_t*)calloc(sizeof(struct db_ip_rule_t),1); @@ -3032,12 +2897,12 @@ void garbage_bagging_with_timeout(enum maat_garbage_type type,void *p, int timeo MESA_lqueue_join_tail(garbage_q,&bag,sizeof(void*)); return; } -void garbage_bagging(enum maat_garbage_type type,void *p,MESA_lqueue_head garbage_q) +void garbage_bagging(enum maat_garbage_type type, void *p, MESA_lqueue_head garbage_q) { garbage_bagging_with_timeout(type, p, -1, garbage_q); return; } -void garbage_bury(MESA_lqueue_head garbage_q,int timeout,void *logger) +void garbage_bury(MESA_lqueue_head garbage_q, int timeout, void *logger) { UNUSED MESA_queue_errno_t q_ret=MESA_QUEUE_RET_OK; _maat_garbage_t* bag=NULL; @@ -3117,6 +2982,10 @@ void garbage_bury(MESA_lqueue_head garbage_q,int timeout,void *logger) free(bag->filename); bag->filename=NULL; break; + case GARBAGE_IP_MATCHER: + ip_matcher_free(bag->a_ip_matcher); + bag->a_ip_matcher=NULL; + break; default: assert(0); } @@ -3131,7 +3000,7 @@ void garbage_bury(MESA_lqueue_head garbage_q,int timeout,void *logger) q_cnt,bury_cnt); } } -void update_plugin_table(struct Maat_table_schema* table,const char* table_line,Maat_scanner* scanner, const struct rule_tag* tags, int n_tags, void* logger) +void update_plugin_table(struct Maat_table_schema* table, const char* table_line, Maat_scanner* scanner, const struct rule_tag* tags, int n_tags, void* logger) { int i=0, ret=1, matched_tag=1; unsigned int len=strlen(table_line)+1; @@ -3193,14 +3062,14 @@ void update_plugin_table(struct Maat_table_schema* table,const char* table_line, } else { - plugin_EX_data_free(table, table_line, table_rt->plugin.key2ex_hash, logger); + plugin_EX_data_free(table_line, plugin_desc->key_column, table_rt->plugin.key2ex_hash, logger); } } if(plugin_desc->cb_plug_cnt>0) { for(i=0;icb_plug_cnt;i++) { - plugin_desc->cb_plug[i].update(table->table_id,table_line,plugin_desc->cb_plug[i].u_para); + plugin_desc->cb_plug[i].update(table->table_id, table_line, plugin_desc->cb_plug[i].u_para); } } @@ -3210,10 +3079,77 @@ void update_plugin_table(struct Maat_table_schema* table,const char* table_line, p=ALLOC(char, len); memcpy(p,table_line,len); table_rt->plugin.cache_size+=len; - dynamic_array_write(table_rt->plugin.cache_lines,table_rt->plugin.cache_line_num,p); + dynamic_array_write(table_rt->plugin.cache_lines, table_rt->plugin.cache_line_num, p); table_rt->plugin.cache_line_num++; } } +void update_ip_plugin_table(struct Maat_table_schema* table,const char* table_line,Maat_scanner* scanner, const struct rule_tag* tags, int n_tags, void* logger) +{ + int ret=1, matched_tag=1; + struct ip_plugin_table_schema* ip_plugin_schema=&(table->ip_plugin); + struct Maat_table_runtime* table_rt=Maat_table_runtime_get(scanner->table_rt_mgr, table->table_id); + struct ip_plugin_runtime* ip_plugin_rt=&(table_rt->ip_plugin); + char* copy=NULL; + size_t is_valid_offset=0, valid_len=0; + size_t accept_tag_offset=0, accept_tag_len=0; + if(ip_plugin_schema->rule_tag_column>0&&n_tags>0) + { + ret=Maat_helper_read_column(table_line, ip_plugin_schema->rule_tag_column, &accept_tag_offset, &accept_tag_len); + if(ret<0) + { + MESA_handle_runtime_log(logger,RLOG_LV_FATAL,maat_module , + "update error, could not locate tag in column %d of plugin table %s:%s", + ip_plugin_schema->rule_tag_column, + table->table_name[table->updating_name], + table_line); + table->udpate_err_cnt++; + return; + } + if(accept_tag_len>2) + { + copy=ALLOC(char, accept_tag_len+1); + memcpy(copy, table_line+accept_tag_offset, accept_tag_len); + matched_tag=compare_accept_tag(copy, tags, n_tags); + if(matched_tag<0) + { + MESA_handle_runtime_log(logger, RLOG_LV_FATAL, maat_module, + "update error, invalid tag format of ip_plugin table %s:%s", + table->table_name[table->updating_name], table_line); + table->udpate_err_cnt++; + } + if(matched_tag==0) + { + table->unmatch_tag_cnt++; + } + free(copy); + copy=NULL; + } + if(!matched_tag) + { + return; + } + } + ret=Maat_helper_read_column(table_line, ip_plugin_schema->valid_flag_column, &is_valid_offset, &valid_len); + //thread safe is protected by background_update_mutex + if(atoi(table_line+is_valid_offset)==1) + { + ret=plugin_EX_data_new(table, table_line, ip_plugin_rt->rowid2ex_hash, logger); + if(ret==0) + { + ip_plugin_rt->row_num++; + } + } + else + { + ret=plugin_EX_data_free(table_line, ip_plugin_schema->row_id_column, ip_plugin_rt->rowid2ex_hash, logger); + if(ret==0) + { + ip_plugin_rt->row_num--; + } + + } +} + void vector_print(igraph_vector_t *v) { long int i; for (i=0; igroup_graph), &is_dag); @@ -3355,7 +3292,10 @@ void do_scanner_update(struct Maat_scanner* scanner, MESA_lqueue_head garbage_q, "GIE_update error."); } break; - case TABLE_TYPE_PLUGIN: + case TABLE_TYPE_IP_PLUGIN: + Maat_table_runtime_rebuild_ip_matcher(table_rt); + old_ip_matcher=Maat_table_runtime_dettach_old_ip_matcher(table_rt); + garbage_bagging(GARBAGE_IP_MATCHER, old_ip_matcher, garbage_q); break; default: break; @@ -3513,6 +3453,10 @@ int maat_update_cb(const char* table_name,const char* line,void *u_para) break; case TABLE_TYPE_PLUGIN: update_plugin_table(p_table, line, scanner, feather->accept_tags, feather->n_tags, feather->logger); + break; + case TABLE_TYPE_IP_PLUGIN: + update_ip_plugin_table(p_table, line, scanner, feather->accept_tags, feather->n_tags, feather->logger); + break; default: break; diff --git a/src/entry/Maat_table.cpp b/src/entry/Maat_table.cpp index 65ea347..3771bef 100644 --- a/src/entry/Maat_table.cpp +++ b/src/entry/Maat_table.cpp @@ -74,7 +74,7 @@ int read_expr_table_info(const char* line, struct Maat_table_schema* table, MESA } return 0; } -int read_virtual_table_info(const char* line, struct Maat_table_schema* table, MESA_htable_handle string2int_map) +int read_virtual_table_schema(const char* line, struct Maat_table_schema* table, MESA_htable_handle string2int_map) { int ret=0; char table_type[16]; @@ -117,8 +117,11 @@ int _read_integer_arrary(char* string, int *array, int size) } return i; } -#define COLUMN_PLUGIN_DESCR_JSON 4 -int read_plugin_table_description(const char* line, struct Maat_table_schema* p) +#define COLUMN_PLUGIN_SCHEMA_JSON 4 +#define COLUMN_IP_PLUGIN_SCHEMA_JSON 4 +#define COLUMN_COMPOSITION_SCHEMA_JSON 4 + +int read_plugin_table_schema(const char* line, struct Maat_table_schema* p) { int i=0,ret=0; size_t offset=0, len=0; @@ -126,7 +129,7 @@ int read_plugin_table_description(const char* line, struct Maat_table_schema* p) char* copy_line=NULL, *plug_info=NULL; struct plugin_table_schema* plugin_desc=&(p->plugin); copy_line=_maat_strdup(line); - ret=get_column_pos(copy_line, COLUMN_PLUGIN_DESCR_JSON, &offset, &len); + ret=get_column_pos(copy_line, COLUMN_PLUGIN_SCHEMA_JSON, &offset, &len); if(ret<0) { goto error_out; @@ -202,7 +205,97 @@ error_out: free(copy_line); return -1; } -#define COLUMN_COMPOSITION_SCHEMA_JSON 4 +int read_ip_plugin_table_schema(const char* line, struct Maat_table_schema* p) +{ + int ret=0, read_cnt=0; + size_t offset=0, len=0; + cJSON* json=NULL, *tmp=NULL; + char* copy_line=NULL, *ip_plugin_info=NULL; + struct ip_plugin_table_schema* ip_plugin_schema=&(p->ip_plugin); + copy_line=_maat_strdup(line); + ret=get_column_pos(copy_line, COLUMN_IP_PLUGIN_SCHEMA_JSON, &offset, &len); + if(ret<0) + { + goto error_out; + } + if(offset+lentype==cJSON_Number) + { + ip_plugin_schema->row_id_column=tmp->valueint; + read_cnt++; + } + + tmp=cJSON_GetObjectItem(json, "ip_type"); + if(tmp!=NULL && tmp->type==cJSON_Number) + { + ip_plugin_schema->ip_type_column=tmp->valueint; + read_cnt++; + } + tmp=cJSON_GetObjectItem(json, "start_ip"); + if(tmp!=NULL && tmp->type==cJSON_Number) + { + ip_plugin_schema->start_ip_column=tmp->valueint; + read_cnt++; + } + tmp=cJSON_GetObjectItem(json, "end_ip"); + if(tmp!=NULL && tmp->type==cJSON_Number) + { + ip_plugin_schema->end_ip_column=tmp->valueint; + read_cnt++; + } + + tmp=cJSON_GetObjectItem(json, "valid"); + if(tmp!=NULL) + { + assert(tmp->type==cJSON_Number); + ip_plugin_schema->valid_flag_column=tmp->valueint; + read_cnt++; + } + ip_plugin_schema->rule_tag_column=-1; + tmp=cJSON_GetObjectItem(json, "tag"); + if(tmp!=NULL) + { + assert(tmp->type==cJSON_Number); + ip_plugin_schema->rule_tag_column=tmp->valueint; + //read_cnt++; Tag is optional, so NOT ++ intentionally. + } + + ip_plugin_schema->estimate_size=4096; + tmp=cJSON_GetObjectItem(json, "estimate_size"); + if(tmp!=NULL) + { + assert(tmp->type==cJSON_Number); + ip_plugin_schema->estimate_size=tmp->valueint; + //read_cnt++; estimate_size is optional, so NOT ++ intentionally. + } + + cJSON_Delete(json); + + free(copy_line); + if(read_cnt<5) + { + return 0; + } + else + { + return -1; + } +error_out: + free(copy_line); + return -1; + +} int read_composition_table_schema(const char* line, struct Maat_table_schema* p, MESA_htable_handle string2int_map) { @@ -397,6 +490,7 @@ struct Maat_table_manager* Maat_table_manager_create(const char* table_info_path map_register(string2int_map,"ip_plus", TABLE_TYPE_IP_PLUS); map_register(string2int_map,"compile", TABLE_TYPE_COMPILE); map_register(string2int_map,"plugin", TABLE_TYPE_PLUGIN); + map_register(string2int_map,"ip_plugin", TABLE_TYPE_IP_PLUGIN); map_register(string2int_map,"intval", TABLE_TYPE_INTERVAL); map_register(string2int_map,"digest", TABLE_TYPE_DIGEST); map_register(string2int_map,"expr_plus", TABLE_TYPE_EXPR_PLUS); @@ -468,7 +562,7 @@ struct Maat_table_manager* Maat_table_manager_create(const char* table_info_path } break; case TABLE_TYPE_PLUGIN: - ret=read_plugin_table_description(line, p); + ret=read_plugin_table_schema(line, p); if(ret<0) { fprintf(stderr,"Maat read table info %s line %d error:illegal plugin info.\n", table_info_path,i); @@ -477,6 +571,16 @@ struct Maat_table_manager* Maat_table_manager_create(const char* table_info_path goto invalid_table; } break; + case TABLE_TYPE_IP_PLUGIN: + ret=read_ip_plugin_table_schema(line, p); + if(ret<0) + { + fprintf(stderr,"Maat read table info %s line %d error:illegal ip_plugin info.\n", table_info_path,i); + MESA_handle_runtime_log(logger, RLOG_LV_FATAL,maat_module, + "Maat read table info %s line %d error:illegal ip_plugin info.", table_info_path,i); + goto invalid_table; + } + break; case TABLE_TYPE_COMPOSITION: ret=read_composition_table_schema(line, p, string2int_map); if(ret<0) @@ -488,7 +592,7 @@ struct Maat_table_manager* Maat_table_manager_create(const char* table_info_path } break; case TABLE_TYPE_VIRTUAL: - ret=read_virtual_table_info(line, p, string2int_map); + ret=read_virtual_table_schema(line, p, string2int_map); if(ret<0) { fprintf(stderr,"Maat read table info %s line %d error:illegal virtual info.\n", table_info_path,i); diff --git a/src/entry/Maat_table_runtime.cpp b/src/entry/Maat_table_runtime.cpp index d145e31..ede0861 100644 --- a/src/entry/Maat_table_runtime.cpp +++ b/src/entry/Maat_table_runtime.cpp @@ -5,18 +5,22 @@ #include #include #include +#include struct wrap_plugin_EX_data { MAAT_RULE_EX_DATA exdata; - const struct Maat_table_schema* ref_plugin_table; + int table_id; + const struct plugin_table_ex_data_schema* ex_desc; + struct ip_rule range_rule;//for ip_plugin + TAILQ_ENTRY(wrap_plugin_EX_data) entries; }; void wrap_plugin_EX_data_free(void *data) { struct wrap_plugin_EX_data* wrap_data=(struct wrap_plugin_EX_data*)data; - const struct plugin_table_ex_data_schema* ex_desc= &(wrap_data->ref_plugin_table->plugin.ex_desc); - ex_desc->free_func(wrap_data->ref_plugin_table->table_id, &(wrap_data->exdata), ex_desc->argl, ex_desc->argp); - wrap_data->ref_plugin_table=NULL; + wrap_data->ex_desc->free_func(wrap_data->table_id, &(wrap_data->exdata), wrap_data->ex_desc->argl, wrap_data->ex_desc->argp); + wrap_data->ex_desc=NULL; + wrap_data->table_id=-1; free(wrap_data); return; } @@ -51,66 +55,130 @@ MESA_htable_handle wrap_plugin_EX_hash_new(long long estimate_size, Maat_plugin_ return key2ex_hash; } -int plugin_EX_data_free(const struct Maat_table_schema* plugin_table, const char* line, +int plugin_EX_data_free(const char* line, int key_column, MESA_htable_handle key2ex_hash, void *logger) { size_t key_offset=0, key_len=0; - const struct plugin_table_schema* plugin_desc= &(plugin_table->plugin); int ret=0; - ret=get_column_pos(line, plugin_desc->key_column, &key_offset, &key_len); + ret=get_column_pos(line, key_column, &key_offset, &key_len); if(ret<0) { MESA_handle_runtime_log(logger, RLOG_LV_FATAL, maat_module, - "Plugin EX data del error: cannot find column %d of %s", - plugin_desc->key_column, line); + "plugin/ip_plugin EX data del error: cannot find column %d of %s", + key_column, line); return -1; } ret=MESA_htable_del(key2ex_hash, (const unsigned char*)line+key_offset, key_len, NULL); if(ret<0) { MESA_handle_runtime_log(logger, RLOG_LV_FATAL, maat_module, - "Plugin EX data del error: no such key %.*s of %s", + "plugin/ip_plugin EX data del error: no such key %.*s of %s", key_len, line+key_offset, line); return -1; } return 0; } +int ip_plugin_line_read(const struct ip_plugin_table_schema* schema, const char* line, struct ip_rule* range_rule) +{ + int ret[4]={0}; + size_t column_offset=0, column_len=0; + char start_ip[128]={0}, end_ip[128]={0}; + ret[0]=get_column_pos(line, schema->row_id_column, &column_offset, &column_len); + range_rule->rule_id=atoi(line+column_offset); + + ret[1]=get_column_pos(line, schema->ip_type_column, &column_offset, &column_len); + int ip_type=atoi(line+column_offset); -int plugin_EX_data_new(const struct Maat_table_schema* plugin_table, const char* line, + ret[2]=get_column_pos(line, schema->start_ip_column, &column_offset, &column_len); + strncpy(start_ip, line+column_offset, MIN(column_len, sizeof(start_ip))); + + ret[3]=get_column_pos(line, schema->end_ip_column, &column_offset, &column_len); + strncpy(end_ip, line+column_offset, MIN(column_len, sizeof(end_ip))); + if(ret[0]<0||ret[1]<0||ret[2]<0||ret[3]<0) + { + return -1; + } + + if(ip_type==4) + { + ret[0]=ip_format2range(ip_type, FORMAT_RANGE, start_ip, end_ip, &(range_rule->ipv4_rule.start_ip), &(range_rule->ipv4_rule.end_ip)); + range_rule->type=IPv4; + } + else if(ip_type==6) + { + ret[0]=ip_format2range(ip_type, FORMAT_RANGE, start_ip, end_ip, range_rule->ipv6_rule.start_ip, range_rule->ipv6_rule.end_ip); + range_rule->type=IPv6; + } + else + { + return -1; + } + if(ret[0]<0) + { + return -1; + } + range_rule->rule_id=(unsigned int)atoi(line+column_offset); + range_rule->user_tag=NULL; + return 0; +} +int plugin_EX_data_new(const struct Maat_table_schema* table_schema, + const char* line, MESA_htable_handle key2ex_hash, void *logger) { char* key=NULL; size_t key_offset=0, key_len=0; MAAT_RULE_EX_DATA exdata=NULL; - struct wrap_plugin_EX_data* wrap_data=NULL; - const struct plugin_table_schema* plugin_desc= &(plugin_table->plugin); + const struct plugin_table_ex_data_schema* ex_desc=NULL; + int key_column=-1; + struct wrap_plugin_EX_data* wrap_data=ALLOC(struct wrap_plugin_EX_data, 1); + switch(table_schema->table_type) + { + case TABLE_TYPE_PLUGIN: + ex_desc=&(table_schema->plugin.ex_desc); + key_column=table_schema->plugin.key_column; + break; + case TABLE_TYPE_IP_PLUGIN: + ex_desc=&(table_schema->ip_plugin.ex_desc); + key_column=table_schema->ip_plugin.row_id_column; + ip_plugin_line_read(&table_schema->ip_plugin, line, &(wrap_data->range_rule)); + wrap_data->range_rule.user_tag=wrap_data; + break; + default: + assert(0); + break; + } + int ret=0; - ret=get_column_pos(line, plugin_desc->key_column, &key_offset, &key_len); + ret=get_column_pos(line, key_column, &key_offset, &key_len); if(ret<0) { MESA_handle_runtime_log(logger, RLOG_LV_FATAL, maat_module, - "Plugin EX data add error: cannot find column %d of %s", - plugin_desc->key_column, line); - return -1; + "plugin/ip_plugin EX data add error: cannot find column %d of %s", + key_column, line); + goto error_out; } key=ALLOC(char, key_len+1); memcpy(key, line+key_offset, key_len); - plugin_desc->ex_desc.new_func(plugin_table->table_id, key, line, &exdata, - plugin_desc->ex_desc.argl, plugin_desc->ex_desc.argp); - wrap_data=ALLOC(struct wrap_plugin_EX_data, 1); + ex_desc->new_func(table_schema->table_id, key, line, &exdata, + ex_desc->argl, ex_desc->argp); wrap_data->exdata=exdata; - wrap_data->ref_plugin_table=plugin_table; + wrap_data->ex_desc=ex_desc; + wrap_data->table_id=table_schema->table_id; ret=MESA_htable_add(key2ex_hash, (const unsigned char*)line+key_offset, key_len, wrap_data); free(key); if(ret<0) { MESA_handle_runtime_log(logger, RLOG_LV_FATAL, maat_module, - "Plugin EX data add error: duplicated key %.*s of %s", + "plugin/ip_plugin EX data add error: duplicated key %.*s of %s", key_len, line+key_offset, line); wrap_plugin_EX_data_free(wrap_data); return -1; } return 0; +error_out: + free(wrap_data); + free(key); + return -1; } struct Maat_table_runtime_manager @@ -168,6 +236,10 @@ static struct Maat_table_runtime* table_runtime_new(const struct Maat_table_sche table_desc->plugin.ex_desc.key2index_func); } break; + case TABLE_TYPE_IP_PLUGIN: + table_rt->ip_plugin.rowid2ex_hash=wrap_plugin_EX_hash_new(table_desc->ip_plugin.estimate_size, + NULL); + break; default: break; } @@ -218,6 +290,12 @@ static void table_runtime_free(struct Maat_table_runtime* p) { MESA_htable_destroy(p->plugin.key2ex_hash, NULL); } + break; + case TABLE_TYPE_IP_PLUGIN: + MESA_htable_destroy(p->ip_plugin.rowid2ex_hash, NULL); + ip_matcher_free(p->ip_plugin.ip_matcher); + p->ip_plugin.row_num=0; + break; default: break; } @@ -331,6 +409,22 @@ MAAT_PLUGIN_EX_DATA Maat_table_runtime_plugin_get_ex_data(struct Maat_table_runt return exdata; } +int Maat_table_runtime_ip_plugin_get_N_ex_data(struct Maat_table_runtime* table_rt, struct Maat_table_schema* table_schema, const struct ip_address* ip, MAAT_PLUGIN_EX_DATA* ex_data_array, size_t size) +{ + struct ip_plugin_table_schema* ip_plugin_desc=NULL; + struct scan_result results[size]; + int n_result=0, i=0; + + ip_plugin_desc=&(table_schema->ip_plugin); + n_result=ip_matcher_match(table_rt->ip_plugin.ip_matcher, (struct ip_data*)ip, results, size); + for(i=0; iex_desc.dup_func(table_schema->table_id, &(ex_data_array[i]), &(results[i].tag), + ip_plugin_desc->ex_desc.argl, ip_plugin_desc->ex_desc.argp); + } + return n_result; +} void Maat_table_runtime_digest_add(struct Maat_table_runtime* table_rt, int expr_id, const char* digest, short confidence_degree, void* tag) { @@ -424,4 +518,45 @@ int Maat_table_runtime_digest_batch_udpate(struct Maat_table_runtime* table_rt) return q_cnt; } +TAILQ_HEAD(ip_range_rule_q, wrap_plugin_EX_data); + +void walk_ip_plugin_hash(const uchar * key, uint size, void * data, void * user) +{ + struct wrap_plugin_EX_data* wrap_plugin_ex=(struct wrap_plugin_EX_data*)data; + struct ip_range_rule_q* queue=(struct ip_range_rule_q*)user; + TAILQ_INSERT_TAIL(queue, wrap_plugin_ex, entries); + return; +} + +int Maat_table_runtime_rebuild_ip_matcher(struct Maat_table_runtime* table_rt) +{ + struct ip_matcher* new_ip_matcher=NULL; + struct ip_range_rule_q queue;//This is for index, no need to free. + size_t rule_cnt=0; + size_t i=0, mem_use=0; + struct ip_rule *rules=NULL; + struct wrap_plugin_EX_data *p=NULL; + TAILQ_INIT(&queue); + MESA_htable_iterate(table_rt->ip_plugin.rowid2ex_hash, walk_ip_plugin_hash, &queue); + rule_cnt=(size_t)MESA_htable_get_elem_num(table_rt->ip_plugin.rowid2ex_hash); + rules=ALLOC(struct ip_rule, rule_cnt); + TAILQ_FOREACH(p, &queue, entries) + { + rules[i]=p->range_rule; + i++; + } + assert(i==rule_cnt); + new_ip_matcher=ip_matcher_new(rules, rule_cnt, &mem_use); + table_rt->ip_plugin.old_ip_matcher=table_rt->ip_plugin.ip_matcher; + table_rt->ip_plugin.ip_matcher=new_ip_matcher; + return 0; +} +struct ip_matcher* Maat_table_runtime_dettach_old_ip_matcher(struct Maat_table_runtime* table_rt) +{ + struct ip_matcher* old_one=table_rt->ip_plugin.old_ip_matcher; + assert(table_rt->table_type==TABLE_TYPE_IP_PLUGIN); + table_rt->ip_plugin.old_ip_matcher=NULL; + return old_one; +} + diff --git a/src/entry/Maat_utils.cpp b/src/entry/Maat_utils.cpp index 8462ba7..745952a 100644 --- a/src/entry/Maat_utils.cpp +++ b/src/entry/Maat_utils.cpp @@ -390,4 +390,133 @@ int decrypt_open(const char* file_name, const char* key, const char* algorithm, file_buff=NULL; return ret; } +enum MAAT_IP_FORMAT ip_format_str2int(const char* format) +{ + if(0==strcasecmp(format, "range")) + { + return FORMAT_RANGE; + } + else if(0==strcasecmp(format, "mask")) + { + return FORMAT_MASK; + } + else if(0==strcasecmp(format, "CIDR")) + { + return FORMAT_CIDR; + } + else + { + assert(0); + } + return FORMAT_UNKNOWN; +} +int ip_format2range(int ip_type, enum MAAT_IP_FORMAT format, const char* ip1, const char* ip2, unsigned int range_begin[], unsigned int range_end[]) +{ + unsigned int ipv4_addr=0, ipv4_mask=0, ipv4_range_end=0; + unsigned int ipv6_addr[4]={0}, ipv6_mask[4]={0}, ipv6_range_end[4]={0}; + int cidr=0, bit32=0; + int ret=0, i=0; + if(ip_type!=4 && ip_type!=6) + { + assert(0); + return -1; + } + if(ip_type==4) + { + ret=inet_pton(AF_INET, ip1, &ipv4_addr); + if(ret<=0) + { + return -1; + } + ipv4_addr=ntohl(ipv4_addr); + switch (format) + { + case FORMAT_RANGE: + range_begin[0]=ipv4_addr; + ret=inet_pton(AF_INET, ip2, &ipv4_range_end); + if(ret<=0) + { + return -1; + } + ipv4_range_end=ntohl(ipv4_range_end); + range_end[0]=ipv4_range_end; + break; + case FORMAT_MASK: + ret=inet_pton(AF_INET, ip2, &ipv4_mask); + if(ret<=0) + { + return -1; + } + ipv4_mask=ntohl(ipv4_mask); + range_begin[0]=ipv4_addr&ipv4_mask; + range_end[0]=ipv4_addr|~ipv4_mask; + break; + case FORMAT_CIDR: + cidr=atoi(ip2); + if(cidr>32||cidr<0) + { + return -1; + } + ipv4_mask = (0xFFFFFFFFUL << (32 - cidr)) & 0xFFFFFFFFUL; + range_begin[0]=ipv4_addr&ipv4_mask; + range_end[0]=ipv4_addr|~ipv4_mask; + break; + default: + assert(0); + } + } + else //ipv6 + { + ret=inet_pton(AF_INET6, ip1, ipv6_addr); + if(ret<=0) + { + return -1; + } + ipv6_ntoh(ipv6_addr); + switch(format) + { + case FORMAT_RANGE: + ret=inet_pton(AF_INET6, ip2, ipv6_range_end); + if(ret<=0) + { + return -1; + } + ipv6_ntoh(ipv6_range_end); + memcpy(range_begin, ipv6_addr, sizeof(ipv6_addr)); + memcpy(range_end, ipv6_range_end, sizeof(ipv6_range_end)); + break; + case FORMAT_MASK: + ret=inet_pton(AF_INET6, ip2, ipv6_mask); + if(ret<=0) + { + return -1; + } + ipv6_ntoh(ipv6_mask); + for(i=0; i<4; i++) + { + range_begin[i]=ipv6_addr[i]&ipv6_mask[i]; + range_end[i] = ipv6_addr[i]|~ipv6_mask[i]; + } + break; + case FORMAT_CIDR: + cidr=atoi(ip2); + if(cidr>128||cidr<0) + { + return -1; + } + for(i=0; i<4; i++) + { + bit32=128-cidr-32*(3-i); + if(bit32<0) bit32=0; + ipv6_mask[i]=(0xFFFFFFFFUL << bit32) & 0xFFFFFFFFUL; + range_begin[i]=ipv6_addr[i]&ipv6_mask[i]; + range_end[i] = ipv6_addr[i]|~ipv6_mask[i]; + } + break; + default: + assert(0); + } + } + return 0; +} diff --git a/src/inc_internal/Maat_rule_internal.h b/src/inc_internal/Maat_rule_internal.h index 57772e0..8746343 100644 --- a/src/inc_internal/Maat_rule_internal.h +++ b/src/inc_internal/Maat_rule_internal.h @@ -13,6 +13,7 @@ #include "dynamic_array.h" #include "bool_matcher.h" #include "hiredis.h" +#include "IPMatcher.h" #include "igraph/igraph.h" #include "stream_fuzzy_hash.h" #include "gram_index_engine.h" @@ -204,7 +205,8 @@ enum maat_garbage_type GARBAGE_COMPILE_GOURP_RELATION, GARBAGE_BOOL_MATCHER, GARBAGE_MAP_STR2INT, - GARBAGE_FOREIGN_FILE + GARBAGE_FOREIGN_FILE, + GARBAGE_IP_MATCHER }; struct iconv_handle_t { @@ -402,6 +404,7 @@ struct _maat_garbage_t struct Maat_compile_rule* compile_rule; struct Maat_compile_group_relation * compile_group_relation; struct bool_matcher* bool_matcher; + struct ip_matcher* a_ip_matcher; void * raw; MESA_htable_handle str2int_map; char* filename; @@ -456,7 +459,7 @@ MAAT_RULE_EX_DATA rule_ex_data_new(const struct Maat_rule_head * rule_head, cons void rule_ex_data_free(const struct Maat_rule_head * rule_head, const char* srv_def, MAAT_RULE_EX_DATA *ad, const struct compile_ex_data_idx* ex_desc); MESA_htable_handle wrap_plugin_EX_hash_new(long long estimate_size, Maat_plugin_EX_key2index_func_t * key2index); int plugin_EX_data_new(const struct Maat_table_schema* plugin_table, const char* line, MESA_htable_handle key2ex_hash, void *logger); -int plugin_EX_data_free(const struct Maat_table_schema* plugin_table, const char* line, MESA_htable_handle key2ex_hash, void *logger); +int plugin_EX_data_free(const char* line, int key_column, MESA_htable_handle key2ex_hash, void *logger); void set_serial_rule(struct serial_rule_t* rule,enum MAAT_OPERATION op,int rule_id,int label_id,const char* table_name,const char* line, long long timeout); diff --git a/src/inc_internal/Maat_table.h b/src/inc_internal/Maat_table.h index 3e14f98..8e0b75c 100644 --- a/src/inc_internal/Maat_table.h +++ b/src/inc_internal/Maat_table.h @@ -32,7 +32,8 @@ enum MAAT_TABLE_TYPE TABLE_TYPE_COMPOSITION, TABLE_TYPE_GROUP, TABLE_TYPE_COMPILE, - TABLE_TYPE_PLUGIN + TABLE_TYPE_PLUGIN, + TABLE_TYPE_IP_PLUGIN }; struct compile_ex_data_idx @@ -100,7 +101,17 @@ struct plugin_table_schema struct plugin_table_callback_schema cb_plug[MAX_PLUGIN_PER_TABLE]; struct plugin_table_ex_data_schema ex_desc; }; - +struct ip_plugin_table_schema +{ + int row_id_column; + int ip_type_column; + int start_ip_column; + int end_ip_column; + int valid_flag_column; + int rule_tag_column; + long long estimate_size; + struct plugin_table_ex_data_schema ex_desc; +}; struct Maat_table_schema { int table_id; @@ -113,9 +124,10 @@ struct Maat_table_schema struct compile_table_schema compile; struct expr_table_schema expr; struct plugin_table_schema plugin; + struct ip_plugin_table_schema ip_plugin; struct virtual_table_schema virtual_table; struct composition_table_schema composition; - void* others;//group, ip, interval and digest don't have sperate description info. + void* others;//group, ip, interval and digest don't have any special schema. }; //for stat>>>>>>>> unsigned long long udpate_err_cnt; diff --git a/src/inc_internal/Maat_table_runtime.h b/src/inc_internal/Maat_table_runtime.h index acd1a69..57c6e2e 100644 --- a/src/inc_internal/Maat_table_runtime.h +++ b/src/inc_internal/Maat_table_runtime.h @@ -1,4 +1,5 @@ #include "Maat_table.h" +#include "IPMatcher.h" #include "gram_index_engine.h" #include "alignment_int64.h" #include "dynamic_array.h" @@ -19,6 +20,13 @@ struct plugin_runtime long long cache_size; MESA_htable_handle key2ex_hash; }; +struct ip_plugin_runtime +{ + long long row_num; + MESA_htable_handle rowid2ex_hash; + struct ip_matcher* ip_matcher; + struct ip_matcher* old_ip_matcher; +}; struct expr_runtime { long long expr_rule_cnt; //expr_type=0,1,3 @@ -42,6 +50,7 @@ struct Maat_table_runtime { struct similar_runtime similar; //for digest and similarity struct plugin_runtime plugin; + struct ip_plugin_runtime ip_plugin; struct expr_runtime expr; struct ip_runtime ip; struct group_runtime group; @@ -65,5 +74,8 @@ MAAT_PLUGIN_EX_DATA Maat_table_runtime_plugin_get_ex_data(struct Maat_table_runt void Maat_table_runtime_digest_add(struct Maat_table_runtime* table_rt, int expr_id, const char* digest, short confidence_degree, void* tag); void Maat_table_runtime_digest_del(struct Maat_table_runtime* table_rt, int expr_id); int Maat_table_runtime_digest_batch_udpate(struct Maat_table_runtime* table_rt); +int Maat_table_runtime_ip_plugin_get_N_ex_data(struct Maat_table_runtime* table_rt, struct Maat_table_schema* table_schema, const struct ip_address* ip, MAAT_PLUGIN_EX_DATA* ex_data_array, size_t size); +int Maat_table_runtime_rebuild_ip_matcher(struct Maat_table_runtime* table_rt); +struct ip_matcher* Maat_table_runtime_dettach_old_ip_matcher(struct Maat_table_runtime* table_rt); diff --git a/src/inc_internal/Maat_utils.h b/src/inc_internal/Maat_utils.h index ac4054b..4d8b63c 100644 --- a/src/inc_internal/Maat_utils.h +++ b/src/inc_internal/Maat_utils.h @@ -82,5 +82,14 @@ int decrypt_open(const char* file_name, const char* key, const char* algorithm, int load_file_to_memory(const char* file_name, unsigned char**pp_out, size_t *out_sz); //do_encrypt: 1 for encryption, 0 for decryption. int crypt_memory(const unsigned char* inbuf, size_t inlen, unsigned char** pp_out, size_t *out_sz, const char* key, const char* algorithm, int do_encrypt, char* err_str, size_t err_str_sz); +enum MAAT_IP_FORMAT +{ + FORMAT_RANGE, + FORMAT_MASK, + FORMAT_CIDR, + FORMAT_UNKNOWN +}; +enum MAAT_IP_FORMAT ip_format_str2int(const char* format); +int ip_format2range(int ip_type, enum MAAT_IP_FORMAT format, const char* ip1, const char* ip2, unsigned int range_begin[], unsigned int range_end[]); diff --git a/src/inc_internal/view_only/IPMatcher.h b/src/inc_internal/view_only/IPMatcher.h new file mode 100644 index 0000000..0e387f3 --- /dev/null +++ b/src/inc_internal/view_only/IPMatcher.h @@ -0,0 +1,117 @@ +/* + * + * Copyright (c) 2020 + * String Algorithms Research Group + * Institute of Information Engineering, Chinese Academy of Sciences (IIE-CAS) + * National Engineering Laboratory for Information Security Technologies (NELIST) + * All rights reserved + * + * Written by: LU YUHAI (luyuhai@iie.ac.cn) + * Last modification: 2020-04-20 + * + * This code is the exclusive and proprietary property of IIE-CAS and NELIST. + * Usage for direct or indirect commercial advantage is not allowed without + * written permission from the authors. + * + */ + +#ifndef H_IP_MATCHER_H +#define H_IP_MATCHER_H +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + + enum IP_TYPE + { + IPv4, + IPv6 + }; + + /* 带掩码的单点IPv4规则 */ + struct ipv4_range + { + unsigned int start_ip; /* IP范围下界 */ + unsigned int end_ip; /* IP范围上界 */ + }; + + /* 带掩码的单点IPv6规则 */ + struct ipv6_range + { + unsigned int start_ip[4]; /* IP范围下界 */ + unsigned int end_ip[4]; /* IP范围上界 */ + }; + + /* 通用的ip规则类型 */ + struct ip_rule + { + enum IP_TYPE type; /* 规则类型,ipv4或ipv6 */ + unsigned int rule_id; /* 规则ID */ + void* user_tag; /* 用户自定义数据,命中时随匹配结果返回 */ + union + { + struct ipv4_range ipv4_rule; /*带掩码的单点IPv4规则*/ + struct ipv6_range ipv6_rule; /*带掩码的单点IPv6规则*/ + }; + }; + + /* 通用的待扫描数据类型 */ + struct ip_data + { + enum IP_TYPE type; /* 规则类型,ipv4或ipv6 */ + union /* 根据rule_type决定数据负载是ipv4还是ipv6 */ + { + unsigned int ipv4; /* ipv4数据*/ + unsigned int ipv6[4]; /* ipv6数据*/ + }; + }; + + + /* 布尔表达式的扫描结果类型 */ + struct scan_result + { + unsigned int rule_id; /* 规则的ID */ + void * tag; /* 用户自定义数据,命中时随匹配结果返回 */ + }; + + + struct ip_matcher; + + /* + 功能:根据输入的规则生成扫描器 + 参数: + rules[in]:一组ip规则 + rule_num[in]:输入的规则数量 + mem_use[out]:内存消耗量 + 返回值: + ip扫描器,返回空指针生成扫描器失败 + */ + struct ip_matcher* ip_matcher_new(struct ip_rule * rules, size_t rule_num, size_t * mem_use); + + /* + 功能:调用ip扫描器对输入的ip数据进行扫描 + 参数: + matcher[in]:ip扫描器 + data[in]:输入的待扫描ip数据 + result[in]:返回结果存储数组 + size[in]:结果数组的大小 + 返回值: + 命中结果的数量(<=size);返回值为-1表示出错。 + + */ + int ip_matcher_match(struct ip_matcher* matcher, struct ip_data * data, struct scan_result* result, size_t size); + + /* + 功能:销毁一个ip扫描器 + 参数: + matcher[in]:待销毁的ip扫描器指针 + */ + void ip_matcher_free(struct ip_matcher* matcher); + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(H_IP_MATCHER_H) */ diff --git a/vendor/CMakeLists.txt b/vendor/CMakeLists.txt index d57f53e..fb50be3 100644 --- a/vendor/CMakeLists.txt +++ b/vendor/CMakeLists.txt @@ -46,3 +46,18 @@ add_library(igraph-static STATIC IMPORTED GLOBAL) add_dependencies(igraph-static igraph) set_property(TARGET igraph-static PROPERTY IMPORTED_LOCATION ${INSTALL_DIR}/lib/libigraph.a) set_property(TARGET igraph-static PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${INSTALL_DIR}/include) + +ExternalProject_Add(ipmatcher PREFIX ipmatcher + URL ${CMAKE_CURRENT_SOURCE_DIR}/IPMatcher-20200503.zip + CONFIGURE_COMMAND "" + BUILD_COMMAND make + INSTALL_COMMAND make DESTDIR= install + BUILD_IN_SOURCE 1) + +ExternalProject_Get_Property(ipmatcher INSTALL_DIR) +file(MAKE_DIRECTORY ${INSTALL_DIR}/include) + +add_library(ipmatcher-static STATIC IMPORTED GLOBAL) +add_dependencies(ipmatcher-static ipmatcher) +set_property(TARGET ipmatcher-static PROPERTY IMPORTED_LOCATION ${INSTALL_DIR}/lib/ipmatcher.a) +set_property(TARGET ipmatcher-static PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${INSTALL_DIR}/include) diff --git a/vendor/IPMatcher-20200503.zip b/vendor/IPMatcher-20200503.zip new file mode 100644 index 0000000000000000000000000000000000000000..921f29435de9a825ba39bee3a0bfe975ff3585bf GIT binary patch literal 86786 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 ERGyUt09h1@i=E41>D@!~3Asg5$@fQXh4paFN z0d_@dU9CtjMWQY2kRJDidPxTT)!czcTE$r_A-iOJ=5Vj_yHm-!TKpsP_~+}ht?D{X zX@tRUgA{rnN(fb35bQ+%Ii@BJRT8!5&$J$tb@}oU7l1r}eA2-9cDk*pVAk_#3ax)v zc$%*gC(#|q^#NKC>?@to2JZg92UKgtfBR!Ih?4cu&vD`{OzJ6tuDG8* zxfxeKZ~~U=?jo=|NBJvseU=rv!kt?ULaWYY>u*_l8{z`Q`X?6_blq1u(Ujrc#MynpLS&IYcw_BQ61>Z{iKEXZE8Gd}PE(0g2h4?&W1 zBbY07@$_eGL79g}0RBlQDL7X6rIH)c-4$@31z-H7i%B#Z+6knS2P8Em4sg?*T*yL{ z-ye_7AE%>C0V0CrO_gb?abbCXsVA3eA7zoWSa^6){!sk+CVme2#F|T)5i~f#ya0^@ z8!(l!Y+0ER+8t_waYvg~s$E&D%Md&mFEzb;KUsGwrowv&ogv2EM7ZQHh;RBYR3#kP|93ECWmO z(pClW?>C=#n#m3*eTGPSLjVWv8it`KxRGIIT7)c7;0xyke$mfWk>|lw)OEXnUqGTr92UFjHeT>9nW$I?-9RhSTCm=fhY9VIE5_ zZc7|=&C)abW{)}c8g$Jw`|@?GB!J6_YAJC!mnkjwr@4>tf-T!zU;-dr3V{`D9m$~o zr!gm+zTl_|GCY*41$(?_oODdL)=;9NGY)0eS;9bi8HHaHiMz7XE{;6nbfLnh(__D7)0i~aH~o)qD9|?QSBI|*3b%Y3S7U=)5p=i1$SuH|-Il+TRdS6|R8Ke^Uk zD?Kc(DAQ+BpUjo?+zQW!#U3x67dk0V^{D9$p zBMS60x7FDZs8rin#9QT z#}=-ZtHv=t6!ncLliCo9tMgE%l-WbP?^_ka{+;xzpdpM6e?)V)rTr8@MS4lCtdWvB z23AC47AF8jK?a#Qa1gMIvuh%Vuo#b*b3?~}WLW?vyu|0}#?%3}yx9>txR3o2<2qgQ z;!DjW#xg9Y%fE3KvWy=K{T({LuT!SU*$O<6qMD7`mR4E=apgbUjI4ys61>NM2?Ig< z*s_D=QXBgG=+nB@g#(EXC27ZNrN2={>wqSvAqh_THMOw%_3?_o{ub&)<=c4+8+UqH zEb`_4t?6WOq|q>3eGa=iwcvODac`5oVv=1ovPu1Gy2IXuQD&c=JSNLqqq?%XrHDi3 z;VUg>9dfXBTFlT|QdD+L&o+F2!&(S0zg{0QGhZi&$k#!QVHk-T%e@1Y_ z?-q#nr?LL&baK#ktH4X*adkrG4I?1rlJu&UNtaw^bvw&K_zXhnx97e?f)L z01<~%piQ?SI1rE-5)csIzmE!)9E^?s0fpJB?f_d0%0DeOu8t;6oY$yKi2iRY_r}I1 zhPWTnnHT&jKazyjghK;Y> z4>#31Tqj6tvLz<%W+hn0~sk4N%l_E zaM|KTq+g3WHzX3v9r<%@`wEQ7R8Zn-i5$n4!<0);A$B3lWkq#2;`|5MN8Z=WqaAS4BScIzfR_+fkGvAk% zrd~UC(9oZMT~E)j4p0x@%DD}vaxU-t3;haEFuiXO%y>nNOi#E!+b&HiX=?dh;2a=h zv%gOFyIP2}poOx$qS~8?vB86$ReVTX>M-R6r3k8IeWtO(v$V9iI9UF(bmfu)X4MRD z$F|rbm1MlDy{j$Fl}l^L; zutNr_b`^r?J>&!jl+NGjqh(F@_2MDbVqsyd(yxiBi^pqKzu{P4VnFu#sI|@-R+d`?FcG`F^8IugT_@lU2N2 zspqbrTX8~nd0c;Eyv-gdxITCnjUXp&oFDTB{a|B4dqrWqX{HmDl@a}j5b==ESPHu! z0}KW{YGkk&O}VWu#JqRRM;dcE<5|+WO47WLbV2^i0kz3!oojw8jfTOtG1!u zST@vs`t9XBdgVzG8!h%HV;?AUi9Ox*o8dwH3zD>{(J zxu^AnoEHH9%-?ID`6%wEDu&vMPCwZVwpWtamPVL=qmN&6W3F`E| zLMoK5PPd3IUto*SY{AyaMz`)M{aU*jMY>2O9{{j%5KLrI((NM9 zz&W-t|+`+<9E%wh1Jscx~hB^qbCJ^LK~z2}ybVA(L;MK|!=}?z1r0qja8*Zv2S< zpXz{r-Y>TjDTy{f=i3SBB6$8E-!HRZC2LzOMwqWj@ehc~WU?~9d7m|*Qgc7EP$5K% z(-QLx8NyrKIftJT4oz0;KJQsK^MWgnBr$`rpQ}Fmo2TNe{x5;u>Qz;f$9NWXN&tdlCZ7_is7mad!FF|~d<83~6Gr=7;7?hmnw;dC`tMw3j91jBYnE(vp{)BS>z}% zH<@7|ja6Yt%AwJ^aMYBclxUc4?vLpu5qfaCd*tYvBx4I6*u~Wjn_n&*1V=NTo`Cb_iMaGvl~tWM+d{#9JB7sWUbB?( zRLaGnLdj&9B0+#~g08=NQRXP4DUKxsP8|3sIy&yYU_m`0Ww_h->dLR&S$p**fS;3E?wdfep2RZkd=7qDZw44i!ZTUH9>->1Y=7TQFEJEf2SLN z_RKn21nLB;{;-m#P=|eoz8baz6t28+z+j;CY|XC@w04a!)~&0LsF>3x>Q*1+sUWf` zsn1*s4cL==gD81L?Lh_aV6%%mO&E)Qu(*EZ!v8Me*<&>(z8hMd^%eR*uS7K9N|674 zB7g^6hj@T4_um-?$w})wTK*poP_@dG&4vg9ucF_!K2jmL`KP?g7FG(yqJ5T9jlmD) zs1`dgePOy{;ZbdWt#$d0hcS<{;$t1pt7`gLL(^2r2nCW>Fa!Q02NxHUZd;#}>_QLN z2Y9+6hvd3f!GKFHyNh3@*$(f4#AU zR~+jLnGg{wkWlkOVA~8Y&M>s2cIJNtM2bzOH@G{pV#VzIC8IBZWEn<5f+6}-DiHpS z%}q@ix^{*mGNAc#wSTmBb40qEi(79c`~m~|62Q1l=Cg79X9!W8bdX4o@ZK5YI8V*; zPib|cjrsdjK=E#*9|-SZ0EqXosQUO7B?4YA6XHaWym&&g&VrDI0SvANdFULt^)(x* zW5rKMH9_y6Z3dfF-iRYCx0p<#0iOM&>6SfOVg&LecxFKGX~OYio9lrBF+PSVB@q)0 zi1ly)>Ljh`5=5d@hf5D9P?SbCv{pC%;W7^5LEH1TsbN9ZxL3%Nv23M5sW>_@tweHP zu{T?%OQqJC69O2PvG(TUZ0&M_a=jhHmlb!LyXQ^iawaH@;p}uRYK&5I_+<16Otj@1 zu}JI;U4e>!ZSDOus+={|D8nm^W zK_VC1Z71fJ>+TH#n1;hix94sDEw}2JiH2}E_DE?d9J`t>lO%f(i3BA`%l5&{s8Ist zoF~|QL;v2Z`{NWF;>dz0k}=Dpjg4qw4qDxzlfb;Na)J!%4B{hHC|A{Y=LEMP4LA23 zI4-1_P<`YzncT$#l<;V;IWh0@XR480MW3ed3Dqz|glU+wP>AFX8>hW|^A!4@xRaOAY4sT($^L#-bN^N1{3Az_l3do zAq0fM=<&Ec?swN(8_mTcmV=Ehmtd}Zz$ok0O2@lPYR4XcyOXOB z10y=PuxjqwmXTSDBC^ARHM0vSRVb$`koe=@;6tkq%sQgmxhfX&zhiZ%XPR!TLG{f7Kp%INq+;hpBU)&%6E4 z1JfD&uJ@O()33x&e4XdR+L@hS6`=ZjUY?(h-_D$K;`!h(fny{*)XfB-O3p-9x7>js!kZlqRah&u5y4Z>?<;B zaI<{U0wjAD7U9q50KsPkkrr{W)1dupu*yG|iJ)2<`UG&b%mF0jzY%=@|A+ir%Sl@f zFu?GP?0)A3Lf>L_$hkmm=(AdV1@BlvXa*{ux zfz3%}g!r+Hy4-v;EA=Q42-pWWbxNuCOC3252=5JY1TxyHJL8n3mY;70N2Z*TJux*6 zWJ-3#)-cx`q#jQ!OLqFC*%8*L+lVP=o{PLh&7CI)Ul!YT$}M}Ktz5?$gZ2x5hb+)rS`&#OFQ#Go`ruq ze?f&H^8(j0V_e#LdoyK}{{5FqR0#lm{`#gKt$;aRGa3+(@PEG#lF>JJG5)VKAYhtz zsrJ7^;i>?ka4TrkMXL@MPP8qncP8ZK`n^8`n$5Y4LJI2D1o_?@VSyw)_!i@C*~aG>OKf`=F{Kx_XcFz-W(=+@ zcV~FN(N;(~PpB|PU(nFt^JzjIMy@?cogo%eTa;j^9i2%ham3+~{+OW73 zffmY9Rz0b45TvIyyMSd7G6qMBWB^3|`*MB|A=OFZSh_othouwA?&!0?+YDhp*0oecg*^DJS&iXgbDshgzV_Q#akE`3uBq{V?ESZ^MRa+Z6k;G@yUx|^frafKw(zkmL z7lpH)BACM=`Mh0qjQsJ>C(BRdlcdkM*XA<%yDuzxjQB?*R=$`8zB+Okha_~JTx=sB z;q^i(aE+k-D=okMMr?H3>=l}QytJ6h4Dr6#cNPq!;yGY!mP-@lD2d~p?OC&*91T2% zHz(p6(sW7um!=|@)61AFc{sYF3fuQ65ABV$uzYbvj-Nh*SR?t|@^isJ(I~p;ANLGj ze6PcD=^i|wa>U-tihr_Bk@EJTs@GC?CLKZBVbVX!tuU8WEE5-d@xUdXvFhtlb%TQ} zSs+HNxucYqbtBiO&61;ekvg%-PAZ}mA@U726Y^P{?=cZ=JXLZL4(PZRAtckH^F+~Cq>}#oNj7SM zdZAQlgyPu~gT-HMgPlg26D=RQz!z@w;17r4)1(1zH7d|9vf{*sa}Y}CfUcNQmKH1* zrMZmSa-DfUC)#JuDND_{u1>z$k-jl4QOX3HVUl4`>B0H@6{#g!BeE0n?b7Mx#?HLhYY zAeg9}ExY4fogtx6`Rn|bp|-23ii})3Z3(B_+x&U*(GPf`_PLtcTsZw21h!S1M-nfU zZCc5hi0#?nPrLl%0~WZ4t$3`6ZijnI6Pn~-^2rkSIK{-$e<{p^QCW5JiE+R2@Dzfe zfFDc2|G528uwfTaN~7|hY{+|Mlj(-FhHj=VLpl-AjvI~4;~o5h`>73> zi)Rciug3erwI3bPRbV$?FRQrRRXKAlyq1pBxU-W>4?ON#D9AQHYeEO6IHfiJsHsqQUb&qnu5%U3eL9NK zn)&LvHz=l{yG*NcW4zJM@MAj^NZ6XsK`{SbwWh@FtE!_@sV^z#N3q|QdnJeQ^VtFJ3-M3%PeL)SwkX?h1B*4RIple5 zG-Ll^)vCd9hn=fTX7pvFYwHXBtgbzXI-UR(AakS;y|%f;yIo4fg&YC1l#^BI>VotaM5|`rX$Sf5Xj)wH z`f-e;k@LUx01_E^IP(aQYLa~ZBFFr$Q9;K4kjUVc*opG~Dh}-k75^h6Fs&RRQ%#vt zr0iN?l;=!sturi-b2+C}${u`wTbH@{Ffw*i@qJptrFT&D?Y@Z}J$xKx0GT_&ToxhS z2tqSz(~RLlr__J(Tf8fU4LUGMP)E&!e} zAd`)#N0)gmvOH&hiHQWaa(k+*rc3tScX*ek3^lOP19p{&1D#~PxdKs&PE@o>^?;Fn-Kg8A_WHLg6>T`2iAB5RD!s5{9Gj zKwLG2D!Zt-5DGGm4bq+>#D^N8`rZzeCkWhjJ2bMU{QeB534aR(vh0tpJ-K)XvWM?k@sn>KD8#;PDa6&z{LO@wC3#W}=N8vNqO%N68DWkGf(es>?f@cv(M`zcsvx=++DtZ=6u@g%008A+&s$ot-j=HcicNTa_rX0OHOTN zE+J$)nmcAV{Q4y?>ob5KwbZ~0tiM}zJhMkk&}oO_2vZu#mI+YiXJ8*9fF=*P3gasm z_p@}HI}a^blA4Nk^S5+1YS5b}?ZON+V+`JzBK9IFS`H~%b|MVU)i)|A#+Ab2fxD_p zfcsynz>cs*9Y0^I4HJ5)@HxHD2VGQ-E12+YLjmj3j=#@sePO?c?3$YzLh<3?f22wF zpYjV8f_Xf4e~*14)-qXgHf`~}W`e8vi=h)X$J(<33@_)-8X@B7t6o;-M6zWP$Uf~? z@H3Sng>T9pp}z4FlzpIEza(5Cwa$71&C8-*NS59H%F2sR_kn&+V$h8q7?|fD-Z|=7 zl(Xvmq39U0U^4va>p{>sOd%ifdxtB?sX!bKl3syQ(qy{+mRbva2m5m>-1NeDO5-mZ zm%|3dU-gvosqsaVT19LJ*iI;&1DB9x|9%)829lay%f!9rv77Y|UpF3C-kl@_uDLkV zhxdOmsQfdn*vP_k6#>8sc>t^s`kyJpX3+}Qw(Ee{o=ve2_)?XOJP-*$s*cP8MzJ+X z#O#~lM(HTKDEm}>{AH1J>80(?n$lKTi*r0=YKQA}cJB2_T{8bi-=GziYq!-5q&RKj2uGmNGZdt+v5YQ%GYX6>UL zs-u~N7owxeU2?n*5hpE5O)H@1!vt!fkyU<&GNcfVH2hqp7B5RjT{q{vlpa>i_94Nc z2DA5pNYS*4cK<%L%cXIT#Y&liG&zO!!kpnNx_8_ytuC_&(rMWCB)WiJOKnH)FzI)v zkd~oNux~^BA?gvh(sj^>{QQ`KBbG>f)1&(jtVk}z5;(zOTehOfM{fyd)WDy)W%tIn z$)aFbPfG+SZyWB&Pe+1SjKftD*}5KZ$OHM>Nau?rjKDbuy3__ia=a5AO2jrwfrG!4+hjA6Ea zg|+k+FF-OI@KXks1&gcfc66^pD*%8ZZ4#`bNfEyvl5I&LK4?rPKiSKYbYr|M`Qo#@ zd5h!crYEbqZoMks7erAMT}y57kna0>ipc#TS8l8b>yCfDE%jyDtQD*I_Z4awU1yj& zFY*CR)@^f(??|7jReN0`2Mo0qJq4#}5u&gPr-Z=ZBP@6_%p_nSt`?(ok$-WIu&=p*0a9G3 zvARAxB|7OMMiFmKkwx=j~-?u$cT~ZW&bS$xq2z0DI zXchUt?fjl=#xb3kisNROlJRT&;xQo#tk305(-o#mfD`I9dGsMJ#C#5W4qyVSoE@KXN=|cBlVR$>B|L z+P*cg-Y4l8{hNS6su(Ay$1=sxvekv*dE5l6 zWBYvgP1l)HA^jItu#o>ucSU@06Jze(S_zxMdE23hmIm~&7%hFT*rUFf|nHRklhrQes*_ngsvuczDAU(++^yFx^|C5yCRw(2#N zEynG6Zn;Bj#Ny6$ulcgih9(JIIq~?nP~6g=$Xm;@5wNU|x5wo^&DIt6X%7t%jz1Wf zw9u$_Q|I~~KU<(X$|ix7Wa`>3xtoM}use+HR0;08jRmpqeGW7Nn|o?&Trb{qx! zErnHbj#rk4dN2A9JnO&yrFHnH5;)z%dCLP{EJDDG^=~W-`Oix5PrRJ%0^ptM6#IlP zl@{D;a=9#to+A{ZG>%kCwz&LZCYXG#m4FUBL~j(l;jM|ACDrcOzxIs64UVaj7lxD^XtqRF8Y>ha`pYNaf>KN*e z!rv;clAdLa9dcqR&cGf~UQntffRFFX(GG{>xDAmw+W+?)J4xfZE_|thumu zmEtbLDrltr+%se{7>pu?`Yu>aH}^>w$A{Nbtc=v6|L@!1mzR^*++DoD3+wQh{AJ#$+`eem?(BOaqdR%otkNaD(#y@kj`27~Kqm&Y;f-q4*@FBngt9UsouL zRO2+HG+|6i0cA#?F;-ZJB$A{32zr=O83E#i%I`R-AYarAi#GXc&@jo9MR<8?%K6-a zy`FVjO*661B38r*4VuWXUdWFBGVSLhlU}Qk>msxp&MLQ$}6jL)N$5GGkZg>5Z2J{HXXL+K1AqJ{&wKG$yB0#7BY_(yh z8F930ZUWfS*SnOQERmZ5x9`(FMM1wI0{eas0^OKk?u22u7{+kzqG)a0AbTSCC95mW zbhKJVh1|3-j(GtIsBnA>v?`pdtBb3T9?nVNFVDoudp$y5GV@uuCn`VVR$dSgjE0~N&x53Q{|o8Oz>yc}CJ5X6+^ z2uR}R_HBK&6ILAt%uTt6PpEASR_~iOV@Cz}^+i=nlLOt{!xLQWwP>MZmzzgA65(hA zokS5o#$kc=tgrQuiKAqn@2p-iG0Ebz%yEE1m+kQfbY#FlMz-v4sO<0Ef@BFB^>tE( zJa`Q2R%#XpV()#bYfC)F>Axah^AOK@IVb2yCQgczjb%}_6+x#_VTqR(SczKXKHOskrzxStn?c z9-~lVA&y!ENQuS<3N4|&x+01T6x3>*MYlCLu_vb}SpvMR$^e#mXX#9e0aKi^?m5dT z0t^@#Nu-&;zVLVQmb}-#(BscU;tqvmjh=&+(2&!fyVjdKB$#M*;YDZ0^*8bT!2^ek z?1$=R24^i#*dkTnazU;g@KYN<9BZ)CC4n=7F08NxWqV4c5E;}rE&|b->=9K{Q3ud^ z))FAg1}zqbqle~YTlfUfSP7fOb&+7+EK7wO-HLO3i%OH3k6$#9z6~Iae~5N_{=_J{ zX(d!C{D8pG($}(Ak8PJO^^~?7{N($nu;$%YsXQ_}Z6}1|#@Eiu?KFnR8F|GpzN$D? zN#rP)b81Oq8Ol5a_FkVHNkK=@{}l`W9#cGjeR7l3p*;Tdde%VG7nxaI(dI*Wg0V=P z8SDO-r6&oC`p&Hj4OxrF`J`v3Akh~IbQGy$>;9Qnf|$^t7}Q~#{zr+VRFGG7YJ*PR zn%u%#^s>K}_G)r3+U6ievtumdC_*!_BJU{2w{doHY^S=uxxN!*zWy9T_EEacS?hYG zO0B z2iUJeFB5i1B4?JE+De}S;`x1f533KXjGK-mu z10Rln8zHl#QAq_)02Z(-m7EXb83Y?d-GI}l5{_|RIW9?%lXPN`Na(QVmYxCGCQ^cawB?x` zT36ePzVyS!X%d%j;ns$rPD*AQPg*pjyE+nYUl))`4&a~nKP0?ByGIA^wyPF}J--4! z8YA|~rsE}#^xU;9D!UBQxd+M(V?bg`@Cao`pZ;y_KqVH!Xp^UE?d8n#^#amWG3w94 z0pjZ&?8jGRfoqKzG@V~xQXizVN(Y#W&l=Z?jIPy(5$y7C${XzF0=ig#+IdgSjCK*~ z@L=Ru6*KzrSygO!LCX6e4(?kd>2p^zS$IAo9_R$5_810l;}ocVQv;VZ zMu?kQ-qqwbAUXuTIwU^^KFl4SJ++-=ZDm?7yS=N3sZn#neL-Ey#>c7yCjz*&Ptbo3 zC}4*j;QPP#nJ_n(c#CnafL8tXTz|7jbxgnZEE)lu%BaxR;q=L8#_B+(AS#o zOw7Bfsjt+{!CLO=fjWq-jO@Fey2Q_Cwr6U2iMFQXu~h>Q;>3n>@%-7&d8C_fNqLVt zju*lHr76&?f#!_l0<;;j{`=E}-%o z9#7wd;sMc{l9k#|;l!T=qY(XyP^1KlZ5Id!I)P{yx?T6wa!r_~c`zGv7f6lNL8V@* z5|(M=Wm%Kd*>Sj9Fu5X$@U8I37~5VUvIDnnZp^RTy+hq~FDkradJ?oL8e=dFe-to! zhHu9DM!?r@#x{=X#s(`dWOiju8R8>J9o}UkQQ%*?i9l_4a>#C`0lYmQ+}P-8rcaOW zrmyE4x?3AQ5w9qD_H_4nd>)5XEge~_YvO5Ww5TGnF&ixD+M1}IgRxNu(p$E-dE9X} zT*_S-o1VDc`v3CB((C#?*Y!wt`2HQxll)MDdL>rFQL$ zr(hN7@5%GY@wmv3fCq>r4BG+H?lpY=t{)vP|0Zd9x2JoNh>DY zlQR*t`Ot?QBoZ!&4FWq5xe=^FmV5?pO?o;%sS47JA#n$Wwv0j?n6%;lw6Gb%M5~Fl z3L$~zW!2vCUni>(_d@?nfv3X(mhivCWr~brya`R)yno<&?^t8&?2B%*w&j#gZFhKR zCGN?C!+g2=;CgXS)J;wEwLp&?FH3?Z`1_49AObtv;`Dr9JhmngiC7aIEv~K<-te!5 zxv2-2k1vBsk>cJ1)@2;g?}6JTzL?^a3!@S40S&`=w1Obw%}@bY!FqjvA7Ip~MIs|R zjzzRCQbd>4;#rf8AIZ?kao2gLw2pxS*8WA&{hdudWSP_PO1zIKE$mE6fp{zr z%RnMZHNSR9ZW3u|X^I%Gf{Dl_DZ)kENt@oppEx@puz(!xHf39AD2B-l1dq;aWXagz z2#5}ekTa4jyHvNuvzdb!;fcN_G>~EBTOP}2ulj5BN3~<2zWKBprp3kyx6P{P8=cL!}Sy%%tN*xqQQ?xoKb7iR=7 zOVi$`IhBVa%zW}CJ9TG=4LXz)QxnhGZ~p1}lVOmGwY9z3+8k96!KY|5)Ux?uZxMa; zQ*;+4>5XAzPoHPMQ?Z!$?fMR*G%5@@EJSP+EHE)J--cLCl9)o^`?PAko=O#o@+k}_ zEh0hqAe4W`9QR^-8+h2&L(D-|9sJpvoal62o~~bHPib9)kofahWWPcC5c)dU<_Q$r z?S7NLF!M2FSW3(uMP-D>k(OOJ=KW~zjWb`s-W*jmm$X;7d{Amdk>pO(VQvr$3X5Bn zg_Nq%9BaoZ5X4qU6qIX9TpE6bsZ;=G&EklK>FY^43(aoOu^3now5EYgekxe_HQ5Ihl}b@|r9Mo! zCw2s1lOwvn+4fH)P7QvvfKX70^l4n*qCn>~zmyRT52~Hx$1I^Z3Cz>T2A5UPtNtGn ziqT$rh_nuEs3!Ab@abh>nIQQZ`nRz2otVChQPmaaA5w-wa1fVq_I&QXF#}7_JHK+_ z_fmE9v}a>b5PD<1lVTkGj*47<+%$%A+XCgm4XqbN;>n0J)3KRXfcZ!XAClbX@$S#x zl1D$n8<>Dz0-0_52RlITEr3vmY+K+WjuLGJJF0QUSP0t76NZ97^5dWLgWrx2gS34C z>tj3lz@B;iKES5-RAi>@o)epj*Vcx{+hFvSjF8zl?m$|w)xA0y+0JAixXi{0cIQF} z@RpxdWr`W1x(SEuqN#5Ep*>1AG9GQz_z*K3-Og#jSA!V>ce8V!_CzFbBRy1Ue%y%y zt#b66X;UTDvKh>C*jvt@aUr?&%883kRRij~B2LYz=TxBUplv1sg?x-j3ndD+b)scP zr+Yr&bsL#45w<9nxZYd=_5eazSxKVbn0Qid{^#$t&w6a=wjBBmyusTA+7WO1976 z;npq>m-K|S_3*Mt=r1(ut_|~08doy^Jf#=;O{$~ z`KyJAZexRj2s<=y1=k7dznVi&I>QjKXSQSQSpGh=2$mDEygC5p(iBJjyKMIwFLdv!6>BZ9edV91_7ABnT!?``p9H5C|7H;@R_>+4liHdZxdg9;| zXOtXXQcYSbPUm1+^GYrp29?FhGV?i-dl*IscZa)x1rO>m=1XsVeAg}X;RjqxZUJ9O zyzH*7E>(qmSA_2%Qcc{5seOuTNIbR*u(=tIu#H?19v34 zzJ-A~{b~c+P|Y=wt<&heH6PE9RF&{gC62^CRGEBoWqL@{BzL5|XGF8D4$ukq@}4Wv z6Sl)BzCw!fn!o>L3)nx{F#l~A7Yrcg?*}wN|DA#8zhFaMD_a{=I>Rk4OVtgrl-z8` z7x-V3QilH41g972Atj#Ch`Y?gCZA>r3z-9>3TsLRk~-pS`ebW6-Ot+<2@(z2D>A<6 zSvYmd&bC|6l0$nwhcjKlkTVeIm-?rs+zZPOs?5U9GtzPCn(EEGUY=pT} z#fb_L<{?wU%lz?ab1WEQK{)z$bTIe{5?N9XjHw8`<9?FNk$68C04Gum!^%dP*GHkt z6Ya{Ez#|NOaA}k<0IXcEF85w+I5J%|Nh$$cu&%*Q+vO)*rkvh;}vJ?CSWb@v&vyOfRcEJYm;*FlM z1--q916%9q-DU=_kqz*l15L?5AXMyJz-?Ul#cwr-uFSgDW_Smk5~_LX5ni`&7Dl=} zM-XoOYRzu()Yt!Cl)Y1!CD4*3oVIPd(zeZ$taPPq+qP}nwr!i0sx&K|f8L(H)A#TB zrhEEfKb@Cz_K7cI?TA=w`R$OCw%fl|TY4?=+PZEGa>(`>d#*V{211bd+aPZ3Ze1Zk ztOku0Ty6!+ce?M`?>DTPYENI`Vr_L)yEH0`EWFuRs*B(qqYgBAZ2Q0cA{`3#+$(Tj z>>^Qmg{$bL*{R7K6hc(EA`J~xG?mQKe)8m)JA)5%!-Ci5E#k65;haDYCs;z75kq+s zb#JT;vw+|1rWe}VAM{E59?tyCeoEf7ZU@^k4AZZT#gzbs2Lzh*^5rRe7Um`(tg3m~0Qx5^|DCdX{PK!jU_U0ly44h0A=;roN{5mUrwf{I>zJ)aj3Y73} zJR$5yN>{W8j}>duji3)`L3)KB27ZMIBts(YzLf?`NsKdnQS9@HK$FrAmcpBMl=xc+ zknr<67#cZ5`nK$P5{5CtoayF-(NG%-Um3)T-onGL4F-)Jv8Yju8?uKS9-dYi{8LuxYSNs6;^qCa0In2u5{E@!i^+jVt^(YSen^h zmQM^ExGF!|uM)H&95Sm)XyN;=dKJRPbxlz%qqD8hH8JG7-)d!!{me{O%~RQk!0_P3 zYOZO9k;T|`f1X(gKB^bf#jX-o&1)X3cj>}05R;_CGe7$r=5o`r^<_OgDDCj&(5%5m!NNjq77o>mOeH1ej#MCFLUs8FFg$g4uqeZ30AZ zurEG3O84F&8J@f<7~UcGdrO7E|JHV;!x_CAfhuJu(hz-?VIR>21rO}-^Y=L})4%sY z!yhGz_B<#hwiJMe>Vv$aj;L_e|Bi;(y+XL2Z}R+*ZmuBv-G?Y3==`gn{08q#VBHZ9 z_o893t7G)fBSd$l!^8e*8dYAJw454Nurp|ucoi*gDv1EB`Bz;Kq@ir1FB=gr0~F%j zRWUfNu5ezIoMWSwz>d!7(wTFu$My`a@FQt{L>xsD7;)X}@*7napTD_8IgI8AvAI4q z3arsDx|Zzx?qAmIp$(I3hFyd8-8oQ`m>Qih(p5|=Yt8@PeYjlU6vsS{oWFRveM>k(`0FIK>^Y0!h*gvq3HOE zUo+7bMx%D)^YKM4&R|9lC*lX@42J+1gk~j0cwQ)p zrK_OP4Juf$gw%zx zy}iSvu8+j^&$-1#n#+w;wX6FKpI2uttTYq8_ytBRB)0r1IH11T;*+8*6ME4fbbk83 zhldLTCCT;)Di%B$R$_J|Bn+y%B^~`~MQGYZG9kVk<67X@`|iqD;H-K8%GAKhFdhMM z`iXMCCzE9h(h5r~$mAeqE{#vppGaO9$q)36c@Aemnu6El=^A92G2(Ju^w)L; z)*oUpccT12ux7M6O!?DNh{w5f0H{)WsE`ig8XaS9=OPa&=vgI0mQOC^yH#l$xKpj|{StPay4xjPDEsauh>btAzYyl#6} z;~OE`%J1192!du%eAEhtfh@lBmhk6-OC?r>^F}3z78fC*u$Du2*qbOVSgfJ+fKzBv zYA;65OQ!9YM9BFh#*YhCR6Vb77t9R=J>V=Uz?nSd>{ciYgK$Wan^qr*m|~sC=YsC2 zlp8L=`a4_@#KoXS;X%rCQ8F+zQWA-V?FGnXzH9#VBkGg6g!@Z3yZLL6{BPQ37q(Pd z)-^x4=OkZx76ME|38(6l%{578i9@i<9Y*q#d#(i+0>TnU!vTR(^P2F5BgLJ}rP}bzn8XXxj9vY-MJLwRa zg_z(h&h?<_d;~rCM3QVXOhVq>w5|en<#rh3WTq6LYnE75F24jpEd`j{UdW0fJ>2E4 z)w%q#xmz>YG9g|c1tLY~KX5f9WvAKUU69sg)9t^b_LaX{Ys9U5S|`LDg;&mz8iyA- z8f2alWGHK&x|k;P$@yfelLtk(hyJMIf3)454)2H3h^(r#s#0{BvNCOQ9G%-)%2z7)JGXs+M{21>{=$ z#fRZ~VuU8oGq3V048#RQlfqn7he_>LnE|1$W)H-O`Mv}vLSuX4hjkDOew3Rj{kh!R z&z-6)OMyb%i(Zx^ujDRg1{5Qu&{)Fgy%_} z(p5Z*CUFavq$8%C2R>GI*x^tWRWyjbI?+NJ4%wP{@X^u$^gL#{_oJK@l4_ z4khP92pm-Z8xRj+QZ%wi-Dvy%yb@;!dcqYsu`%VGJVnMy2CkUxCJelz-981C5X8V) z&uHllv>~d9|?rtStDDH$!2&EhTBGh1Uc4`ifAU~7YI|0>_-g^ zOZKPMYG0v?kTlX#_2;w`Exk!^NArrGRlTjNn?Q834}bUwI^3dm8c@tU=26&4*fHi_ z$vl0=e6Cmb5b27fQl#?3VodImrkt&5Mz0-~0oj-G9NS>3lduj6t$fu{C@=a?PS+A< z{5}@TGezEVD*g2lB^HnCn9R2$Ed4Rx-y|sfj~B&*c+p44{c+Fo`JyH5;=-JfIDQ@y zmGX!gYqEX;1RlW!{smiuT9%n+VOnJv{_<#wTe&>=LVd+m3f0;)?E4~}nSf8wfcmRm z?5v)5AEN0^@6&TmoKbl>kXma$e-3)cEl=L5P|CM~+t_5?-RwK_r9MC_l z7S-PXx?6j087hdWHp?RA%((M$l9n&Fv_^Ic*W|HI@OoVhrq_&OQ;DWiYnf`2erv}; zQt($Jjb`)h-^136kT35hiRCl`93$QY&zfWta7TbON9Q+xsxxn1s2B;4U8(~~L!n_b zLm9R;^|wn#?={b72?#rCp3nO68VpfIF6P-v{@l6&iGKgqzTz&h9Aotz8GJVD=2h_q zzeCCdh0;n*iRE;y6%%GPg$%Ccu@R<;@h^4PBj4~)n<*HYND$y2CqNJ&jofGdYGPkZH+!u%A_=%muJH5<^JqBeUtF#=OV$GKy4Z3ZjO1xC_|DaW1bRM%e>zOd#bS2^1 zeHZq-!HlD_tX#psQ>vS(y?% z51ZsGaDxXryj@wK3;XYh#3M!)T^LVnquftir_Mxtty5zcCxRmzifca>YOE>n#$T@D zhPED_>!)r#38>>DUEXwZGgIq23E#O2qw&&2H5gW=0*{u~FMaA4meCeY#2rXq)Vga` zeIr1wq(_VzB*yiDKU7Zgt|&Z%qbI8dxRI3g9?PoI-N>G5(9jK#og5QAN80L^^Z<#+ zd?e+nP9XW)wX{NU%GG+p|M5P7N#Ds3Z?8bmS_KyYp<(VTc@b{8$|Pc^eiZBV3^N3K z6L0gt@;XLCI5}g=t|PUVuza^38FR8}>j-ZKL1l&vHX#f<>2lF|!m>lW&DTxSZaFaU zaYnc>@20ug|G8s*6C&IiWBnK!LqXR`;_jJ9S<%SSu-NOBAqvI6BSAk78{dudm;aa3Fv8&!)1N-gEs>Y^!O2naC38>brJizgZI z0Y_fIhKMpP2cq%gn|tB8{Ekv>jOia6{#H*XpuLgk{JI{E>xno#eA2It5Z zd2zkVhMJcAZ+wOtY}(Bct2|en3w$;5u;OCIUhYU2b3gQdrPhjpoC!NSW8aY>bg$*7 zYOFa_i%!wOvy_>b!@N3RD?{W;)R$3LG5#H(?2znEU|~WR;@`bLQ^0>@s%RL-K_G=( zAdrCV`IGEl&o{$WMgHDuGX}8@32M{&aDy%-^V>`4=jL zA)2wd*?Q`sK(O@v{n$K35xvr)S+$vnm$3Vg0>TsBib@kK#MewZUvY&k)~4z4Lo^AE z{@_-b%&dXR<~s=4xx;5Ji2>dhgu|=)pW&#bPsIQKl0`W~zXs5^l0p6VVf&xXu>YxL z5Dbu0_=lFkYVSa(tY!yABBvS=Qvp-NTto}W5ID8WAS*Si>IN{S504aPax5&Q#BJlp zdrq)q0_@fga1tL=7Nk|z$YpjTtJn)goWLsHKm$5?z5bW3Mu}2ZtI5!4bp*!r9zxmb zqDrPC8qrN9s#+Vv=45ZzEoVM?Ve(A7%nu=!C_SU%B;n?@s+46x#j_fQ4Bbu+^N5la zLZJ805^aGaEHauxV^D4TOcCJ{VSv#@V)oV?(WYrXn3w~C(9qheLT%}+H$gK1OYKGR zZ^TshdzstnN~oxDHUUA7!7KDGzCYzwP)y`F%d2O`pv~uG7NtJM_)*)iGHBm_qFkg8 zCuCfIud(N0{m*O;|E*uE^1U1Ie+V2DtDoC!a-)1+f_Ou&K$;f~VXn8c%2n;P?KD|h zWk5(pOq0OFX%<+sqmToVl&^($yhG8P8;wW#DluX>vtCR%v#=4CcRb(3KP3W*wC%XF zp^Z2x{%Ed9HLf>v>u|aL;51f#K!-J@_|vx)u^Ao{Njk%`0vVH$lwIqkKEsZCo&yGe z(69&h#T@5)*|bn2a)YgMcH@A{J#UD@8n;gCXh^K&;N3}|P za3)tttt+xzL)$U!yGpjopN=~E>c4l5FKdqE)~`5;Ez|i2XRJy+?3lE9R(Dxn5RX1- zZm5xksL<*Kuegx98t+laQEDVcjWZ;?X$ny2FqR4p6bt5wP_p!TX`@J?EENl;`zQQT z*TEj8!kNoKB31Z7>D0!@>l6x*c)% zeen!hm5XZTAJYjIv_6NMCkV{_@J_27AJ-fgouFu8-+q165IA0k`GpqBesh)TCU+E~ z87&W&cHQ5V&in^^3+B*_9mnuwJZ=%jO`8a$lcYz*?T{O2Is}!m%>A6#pIt5%cYK94 z4tK~fmZ)dOQNQ`*z2QU&jl)@Ev|)m5M zLOJriwC+shm>vkVQ%ZtS!OIuXAO$cF?8CXI-mfS}HG-IYxKDHa^d3_+WNr0K$@PFy z1EL#n(WFZ8;S^i8@P!hq-Hk8ah{?daT_YvMcq_v$0Ek;Kr{!Uzlg8u+n;CE-lmb`z z9{;2%V=Uhk28{2gMd`9sjGueZevx16^{3gnXULovsKxkMBxCJUz%^HpNRkg3p@SfJ zN<3*}L);mMLUbcCYcu!PQ_9CL|4FMAJXAfM%4a4SMM|w>^V0%WIokc*nY*hkU9r|?T*oJ#_j5b#*mnLWq0>Z^oDoOC0eOh5LDu@QkgCg z82olGY7tKLFb&~~LpLYW0@5k|O-Rq$+Dk5WK-~gL;s-_j5=gP(-THkYzpOv z$j@EX)YR=R#5^#g*>*LF&WS)+s>s;-oNc8WlyxCa?iyGaE z+WG+|8P^vk65??%kx(_2GjMx%$ME#*M(*vWD$Y9VD|*Oo5N43k$LV*P)^MpArHyPr z`0#mK%@H@;f+c!5g_`ZuI1jWW+>2HgVPo0NHW@QKQ zcqYbHiSTrTmyR5r1r>keRI>IVtDuDy=?`r|S@4a^1Mm3-u4NlchNvSgi(nP~&+D)g zC1ZY6CzNb)pv+n^zgL%yy#h{uh#XJj0L&n!aC{Jb7lyEZ4F zI{FpkV}_|8)kKaw3VL6mWmv+M^~Y?^-8=$}VRFg8xC%pYv3Zo2URn?MZZUM;R0P6a(VZ$Y;Rgq`rL*L2#HWk9_%|ZGS_3G0 z$Bf!x$yZ4O@~q;ACp7<{q*mH1xjTzpkzuC)XG0tnjJ_7?Z<>KT=KqXJ|9^oK|876A zt-h`F%{J({mfa;j%m-}`OO8Vlw(mkk5l)8~yV-;_!CL1h-btp5B~xD5=GS0&mGlmt zXt@{~(f1F^wzj<^&A!k6DkS~5U)>!Em55a#@Uu^S)v3;3ZH9Sfch>&usSGwyIyWPn zBJ^b(iShx{Qh{LXCq_<9+Ya2*xg>H5FI1(cCV%G+{$}Q(tfhh}69?oXA3UM)PC3su zV;?z@IAwZ_`9l)10)_C}h)J4t>e^Z;=@lE`!QRAR!3R!2X$f{@EaWSEgnJS)fA2Qs zAj#Y{Y){nNCeHcSumk<*`f@mYoxV&Gx%(Y9oi-x)G92Y1TbBknPG<`9Z}^htr%~QB zp2!9Tp4$KMhq?kCO}iw|pZOQ%*C;JaMB34ouV`8DIBFo1CCH;RW{x8@22i`f5bm*T~j&beSE7KcDEeuHpdHO&~i^_lgLP`QRKdkpb-(tDw zi&MPr=-s3mGV_jMm_GVS-rQZjEFKj!nud&Qn5xChswzPX9Dh zrb6U~ytGHi5!?DJ=1aLTp?h|2rXcoNH*6W4RY568jeTxW=2{Rmt>Jq5pGeT1241D6Cx)15QOa z3ApSi-jZp`Wc<{P%h(U!Zt1dGu;CPy$G+lTMs)UA>omO^cgl+kHKri40^64zK1pjz z(hxl6gR04%H{=%a9&ju?>uQ8Ew$FSwgWB;df#azH=d&evqU0f;pp^zk1hgC9>YTn) zQbT&6sdx_lpS;C7{ z45#ucq_X$dia9P-Zz2S3WURk*hmc05`U}LZY@u7vlftUT+pOxLt9n@Q(sN4iGaT)!9u9UcOpS|`l@PjJy({Mxo z80+{%d~v7I9$SF2BYsmCaBRu;+6`_IxYTjsp zkBz#ahgCk9aHDgvSz(xxcU3c+>)!u(E<0(a0YvAMa!$W#$^_q>FUtS=x%@4f>11eT z?C=k6*Rw)@FY58dXGk+`muXo{v5|KPz;8Ft=RiAe1u;^Zv&*NeN)qTnmmxmn?q+iyNFfY=kBb>himAL_Qg6ym6jnNShD1V!^$DbP;+u0@UaFZ@DL4; zoZ_ho=7{F?ZUj2EmYZbPPD-*HF&7#pPBt4=R1DEpxtZI#7JbXKJFE?k43I)DbmmQhF@oNvRmNb0~qPfggqA}pz`HHxawlj z1aHWeYDw@@@6;8sKys^|*R&Ek&*4b=cPrVPjWMKl_$2qK_4v47#N+%Bt%F@9kX%pW zbSlcY27##+wlX1@A81+b8d#CJJH&*+-R@mX3MSNFS$DmL>fsYO3%E{R)qWn{D*5J` zb68{Z*N|<5VvzfJ8C43&QKiuZs?DJWCm4>YUN$S<(r>w=izb>%7_8`=&zJWg-5h@J zm-F-83Y+FtVuAvy&h7?Xx+d*Rkmkcm4whG@2-zi~wvaB)tu*`N;8T&erphfUfka*O zyc7kI3F@WL0^_r8Ej0xZg=Qy|rq<}D#(55KEAec?jAXO@t4^;`|e5n!Vl)VzY~->);&v2!naAR=GCl0RoOga=XzBH z)xvePz|tSunFr&@oW*y z9nxAFK*Sq*n}h5rh-)~ia8pCm-1UNh>b7tp-6@p1%@J8dNfc6aZ#ae<+UC zQ@i9EMoY5?f=CA3GJG3BMvKelAJ*p(*p#v6n_zy4ZD}$^arrdvF)#7whtr5D<%|JU zr-d!xefDo|&&%=9uJ5ODx6Z5XA#xj0luH!p{hS$)bDGR)mtl~D%gu>^ydQD4tBR?2sOKEsix@ z*cwgRT?=N7!1A2_d~ zZon%HxF*#h_a}iPAGL#O!PkPJ@`W{$4>ro`=SP#blZS`b!(qSu+rv%XN69uHr+`H> zV#f60pv_m)I=6YwyMBIw0?M(ey0^hrbmht+hO`{?K?sw3us0I0OCLcLB|-@Xjn*Wa zc_&Crol3(Y22I9chzvf3$35tXv4|mhi{ZtE_bE9yp<(-)FP8DfCAfcj6&MR3K4@N0 zb&9$|hoq3LzO(TsiO<*lj4rpxTW9u+jZl84_=Nj7xnYk{w+^_TLec9b+<1~8=0d+{$pPJll~qdC&jXUK zfq0!)SzM)$au|7cxD2TtN%;*tY>?Wucl`V3-RK}0`2oe#7!`ix$VwNNJw!8CJ``=l zj&-gCVBWO$Fw>kLe?+UR1Dq>9S5;JVn7F^7&YNnx3rn_iLOZxNS>q*2#)QZJ_U;Z? zRAh;tC4{TKKt5z1$Nr5xB_i|B&4~p+UlD#l7s_@I5`Xzso|Us|=p)$WBoc!ILD|xN z4cysxPy7&E?>SYzF&OeHZjSi4n-VNN0;jo=`X;(p9$}gvamh_D0lf`Oj5WK7`Sh_@ zvCR9n)_c06ljDtt&|xZYVcmbFWu}!xJ1r-v>LMv$^<31gW4B$Dxh1no1RZFWEo>=y zTI;?i#hG0dl)>ZO@32m(pTCuqPwyX)(fT}v3*a*!R<=I3(-b8xoe-D?&F!S0m%Ml+ z2KrMm8q6=7{KsFwz&4L|(u_QXwxmJbEYdc|QM0Z4xT>eXB+*z?qhJWoI1s21@ZtV&O#C35qI$af}5Hr(!AyWpFXaP=tSmFi3eapIOe#l#l;8@MHDKo?__4(Cjo;dtFxY#V{Tc?o_=yPmCe>sg6t+UpWcoU_DG z1QFMxmP?+asH(X-^>T4XK0y!=1iK3;dq2;~wR)!Se-2t9Uh|ykE)&AcF{j4!&<3Xf z+@oj$%=+ywcX7+5eNe?(n`9`+W-8hB7&x2V+7gqEsGg1$ic z4cLN=+mK z)%k6@fk+7m$S4%D+t; zRjhTHU!EHIC+$VPrVQ0F?tb^$v5ytg08P;*E`#3%&k**%N5?L#|MpA&fsUiUy*o(7kob9&cJf_* zYDOD+Ug(J7WpNG!)me^*(0u)oOlNx81dYj$gUXcYeH`66CG%8T_RS>>4B6dX_POy(r(T8uyAg`8?&8FarLUDv z9b^U9$_5FS8Uv3$pXe3UlH&@RNl zTaOZB59G({jMzoOqXpA zB1TIbZ=TTsKiE&!$VQr90JKa*j6esta=CY$dR$HwromnFR9qfo?4p}?lk8i~$tsu$ zwPL)w?tO2za`>grgKr

yGKBLN@uO4tDl@1cVy#zs1;3+gV0bEL|i2bRE8T>RekL zSA+1@r^tnwOWUtk(MKIhuZK8hP+4*wWm32CY_==8-umqnOwgmRxxU%*DU76#NE!Z8 zW$QGiTqHX43!s%zY5i9IK zvAE=R@(9b4j_U|1-<-^Vv3wT~s#i=fCF+;)UEOxCclkOzVHYD1RK$H8t2h9=P zy(?sg!Y5RbTf5X$a(p{)_peB`UR!cpSH2k@D%^2xkTn00IJ8BFC5CDN5_kM=J%Av8Cbl!E1o z&0Q8woxPuLo4Kw6TOY%Box8p-FR_HauO}aWuXnn?a2e2RSgzfV8aS*)KK3*>Mw%CB zwe*mhfegn8Kmt)OxWUq7;h}Hug~^2^3pjSv_#TDpXsv=`%~C3h+?!~O*n(pGN;`3V zBmH6kf2lUWWSJyC`zIVvj7ZKzVZ4+XBiC5KDk&h?Vt+0!xYwh@9*GOTQw_JKILr(* zL$=1t9^g88>$ymu^?N%iaXY#%Q7n@%@vr`rd_gdtcjhr-17(^A^Ua_B$%jKj!rRA@ zO0w*hoLc&1GDGpBRi8kMT%1jxyG-o?G|o5V8Y5>s-_d}g*m}iaQ}G=6X7#kQtgN$5 zDEe&Bi%o8W=tqYO_JAH>dD%?mw4-xjcO`@Atk4uSY5j@~Pn~1N!aKy$1Gn<^fWLBC z-FDqd_EFx4>R*TIn%^aJ2hqqn`@38oedj{*|0b*dGY^^>D_dJ`G9rCu34RWKtM$s< zFVx_Y;u~sERTIM(OL07nQ{4^o*O8Ns%Xim*JZ1l;mD3VAxbNouyXClW+O!&c6PQkq zD`I|}$Dpuh!28MChL!6JS6|Fhq?C4C&i=b$U;tDTP29xJ&`C9kPz1z zW?%A0n;JE>D55@luW&o6-+&4Xd-|$CVag_AY%|%uN_H$?tS*6F!&>GBBIkfU+f?Gc z#Hk-LYykl1B=OXcIp{?&+-TT=i{DUOmslcDZn0`bSYVQ4c}{&8<=J%3ih1!v6X8V| z6XC26svG`ej@`pxEnQ+d?HJabnOScbM%AkhhEB*~5fP}-_@9KvOXp7=qfLs?Hhb(b zSG~VRTo;!w*n5lxc-)n3-+P2_`*^?RMUoG*?U->*BtkzWxpqRwNX30{G<2}T3o&PY zf)DPSnZ_2p`GrvwJ#^Gi&)+xBsL)Ev8hc~o+TeBW^6#vJYxV-F6h=d9IaMsB_nHO~ z&yVqw8NqLKpT$0Dis$8tf}ejxsE)Tf&&czb2O@rO=Y=B9*h{8;LyXEEzWh47WX z$_0y>*=?=Kh^lnUY*#aXlZe2(PW93MWpSjCM*u?0Q%T$3}a3nk%*&Y-Yj;D=Y9A)p6} z@QLQs(GA3_DQr@&(7iFMrRAv-x;r+$cBo|a<6m4HOq9|9oDthuq~9Zn_5YVT&;Gyb ze6ZTCC9VXLPfP#TAcenOLvdn40CWmD4NuJvyQuQZ6oaXH*VYBP_Dw#QzcY$5ue}fX z5A6UB>Y`xb1%5PX0_ok?q=l0NVe?(?&ep#&?BR7)-RZU_J=xjm-Jh=;G|a!6CKrSA z?j?ySPxCqh`@6qIyt3sAdDH?LxH5(u>B}Ne!ug33^pHi(e%;WrWk{e8iGT=RStnend!k3LzfJsf{6gP=QfhHn$Kv8@9g0e!_ANysM zA9zOn>gLeK!nY-HV{ec_$9V7G*O`QZ32D;@6AP(MlXMd|W2R?jMV#Hd1N6C5?v*R5 zQs5T~)?XU|MG_mpnaD~B)reFaP#S;|JUmbp4xnIS$A?h18WbDZEgy0;KtWOx0twa= zOV@$ql?sERGC~ODGJ%cI}qdfzr0H>G*43KQy zlJ>QBwtmDg79@0Wbo5BF7i4_Qr?XokrAFqHL`P=Pk>5QWw#MCfSG|E zRmMn=@E&JDj^l^A-s)ae!X`;yY96o?Hi`fZAi4vfUjMZXHi8%qy1Ks1y7F8sMC9m( zOibMV+q`nZD=w74%{tA&7e71{80D;ZKObuNr4sm)*&-m2_NgvV2i}f6EC0@H1G@!D zF`9_Bt0I6E!xyZ#gp6&aKN+0p{MB`oT)fl$eK zje26EqtRo=+~EdA?Jb>^Zi0?hG*?o=%PN^Xw(4B|1L^@-)){ud;$@T+w<+0@)TQuz z1l&e?3B}zrJ2O~>P6{*&$xjQ8Z2e~BYHWD!v7FVsH6uAP;_wp&wBKtL7sq=;*zIO3 zZyNiTTLaRa(eZ_bcj?OUOB89-Ql^6z8JXolT&2XC*G?<2y}_FlDDwqSdM#YGa^ov0 z5NjsB=H|TF^^4_)&gK+PSgK{|zs6RCU4bYduFe-`0qLy~{tg9!)jbtonWDlu`wA z_Ua73gwvNS#bxYt$XY6MYh%J}J1VoyMXZKJ@BN|7mITLmzZXpviC(r~uxza;o{@~t zZ=1o4gQIwqeAwmO&PxU>0eKbeoWUa!35i%(Jk<+X%Q9o#=laNcxe?$c@`Sq`mzo`y z3w)rBn~YuqDXVkznxsjNP($a+ij&Nvt3iElp!_pK(M?a6(Ky zZPIdF$XpTbn-SWJ0pVIgyix1=n$MfOLrTeXDmF^?)zQ4;tW@B=JnjHuNyiqBGz+AT zJH*eyedlWnH!hg}1tfm|3y`>3KCOV0if@R~K061m}Z3`V~yAnYmr zi6o->D_3xRPP==z{)r?iCy{P6j+2XVgv!9A3uQ!3gVrJbi6rE3z5gpDkyY6Q8h`Q% zjp}wl{bz#BOrdk;WhmzM%fYw;GQ7QvNN8ou(gtYk2-(ld31Ts>Jmc z|80Fdk^AlqW*FY_ixN>a=I_9GRcJ?A3=~e=+ff$1(QD8p+*q1#P$63=#s}l12WQ+oBQ@Yut6Vw_hI5_9^eds$ z6u9v5WKA{E9(`6|mB#v0GX4-(NTB{l^PQ_&5+P#vufCqazMDm!n@I1#vGlpF_qb+I zPina!RIlRq28kx6vb0^TWFCa%XlLNi(m-RAX~lOGi+wvUGONn4=n5Ey3;!6QT@tGc zxw(^P96Nbr3LF=+9R%x8#W!SdgwnBC^F{N2rMc59#{rCSTG5)mgT4Pd*ops3&@5~F z&1L^?8>sABE&|Ye*hfwtOmJC+mONdrb7M^z4-7G4`X;L}c;Fp=Km>e5nz^*#<&fqlpBZ=_qk#cx`~g z>rUua!W&94kLr_leX#C){5~;76lbh@FCLsf?MROS@%U!|`qW~ZdO-OUPm=T{nZ`3@sWP#r*Nm(>2{X7=i*6j6| zMYGFLA#ep@1!D~5L&qE0S|rgo{U*pu=qGiMF%8IbPpmp{wW9)y2cX z#HT@>nxr~vUGCg2EC6*%5|V-BtSTq+7M zyUTaN`0{+Ebf2%fT&Ex4UQT8kcx8z)7UDG6_QE&6hkXHS8W4D4wqy2!(&%rPDHhPI zY^W(Q-mJKEj<9M!)#z(UOg3B62IY(><2cpjv)OFNAHb{zeuMoe5Ll>OUk>JxQOu+_*z8XC)!+*_Ldr5>bUDpju8; zxdfR_8jYfdtbon05MdaWjLSnP^t-J;@PtmuTiH+4^#bT5^+cIdUCUTA1Z7tel_AmP zBv94QRmJn`lab6_5kU}l-mcvH;A_&p?>n3^89;uuY5hh|cx7ok^wqhI3U<(nak-FY zAs3$#ykFgbqlUx0Cj8wo`}m0aAFWmB?~oUxgzB964t2Nh_A23j8}eqsG5r=GOh_T$ zZB)k7VGJokLF6K<&fwyRm3ySd#BG182C~J!9F=1&k8@hbZ@up&Q=<8|3d3 zR$l_75{=&w5)l^EJigpinAFNC7D{3rE)5BtWcwZAEfA4%*<#N z6E4v(S{t~Rq<<-x29Bz)8=}WAIh>#Irm=y>v+&QZU*ySE3l3|(CK&8fsB6NIfUc7C z^1+e{h+yEaz>FI6C5#m$dXPSxnew!VqL+FPpdG#&VE75hGHJy*EL}o_WtYz3KVn$P z0l(;%)O>@*TUE6)N1S{9 zbK7>=MlDrnr`xVEtO^v!)4%VE96&MRhxl+&!cZ%UDBz1}Rs%%gYBGB$IP=6&TQ`KA zXYS)(`0}Pk^>PEVInj>U2yB`gOROYca-o5t=LpL>BR5Xt z|JU4AMn$=HZBQBs1(6nz?v!rnMnVu#y1PqC6c8i?6cD7521#jOI6c%=^BI7qC! zReeCXvXWCJ>KRKv5iF59|3m~|cA%r3fG3*iR^ZVG6Y{o2eUGTX?hCRsil2d6W^wjb zW=X`a9ZA~bww?vrI)ut!8jIMijhjty)S~3vT_?i4^)ia*kcTCPVJ0^F5qd5@lGT;s zYQg=w=ZE^Xwn%21`@%&^=hhzWS{JT8{Hhw~NvCfviF~k+*=Z82xbYFySLo{5IJwMO zS*IFJ{22vB8?A_@-F|i~A-bU0y1Qo=6|h8W>m0dRF5&kFkTL3C86n8lxK1d(=Q*em zCa<2tDKVj;AJO(M>2l1iiRW+0R~7PCh9Sy>BIQM@miOT-?E^i=Jg~`jdi6yo3o{!7 zCual4+s<}&)=npLhlx;Nv(4ubP!3dy;*G=Oa^9M(NSEmfReB%u@oXBxktfEDOvpMG ziZv5{UsayFQrh|&n zBDR1xJx22p)+2G%J#P2;yC@3mWgZH7DCUKlWVBtDq7pl-MLz785E|KA5x8<|DD`pR z`#ceIT^BW?hJ+^J<*>7Hw=NW;bQsI<6<=@ikEKwyMr5yvwhy5qv$n)`LKA)~q&|2> zKlnS9gr z4vZXMo@r#1GpD@w0omP0vipnr8==n`Y7N02YU_x`PLd3%XUTEMmu80_jo8Uy3eQZJ z)=|;w@1#+%@fK_bInD}=ze>sVJ5Y*p$TSh^uU3+*PsF~*d>m%6G1sG;*^?YR`;vG-{#vie_lebw|F%pY-h(Mouo)2J0mA>k5@_tYdE z$==?U#izy6kMykRl+`Od8etpNZ0Xl{NqYtr1DEQ&D>$*H=5hM3VPIUbLm;jTxS@ZH z>F}wCs@j4Uxx2(UNm)c4|EbpM_^pF>T$VcHgUMSl_s>k13r3;NR3>A;a3H~{2%HWo zO?BAi{c8k91%ilIdjHlq&?~V4z0&DNG=2{vbC>JBb`w`|54}(h$gs&&WvvWrbae5R zZYs*vY50A_j|(s$LL)n}2e>!ULUdCCse7MfYwJWw*Sd5qtq*wL*e)oUB=JIE zwAaWI>$~oTO0UK}UX~Sy=v1e-D8(;kYJ=$@jT)Cw?3GWxbR6y|Y&l9k-OSh!G*kM; zq`n?ypQd09N6#+D|8_R4t5Z0tTWCUFd}(= zM?u__nh<;?^S(NuC!t6F^CbBkQ9(pYm~+chgD|3NwKv+w(9+O11Kz8Ytv1pWiP{+FpXpGHou^uEw_B;iJ+B+b<9_M^QpCu^q*FY1@_c7 zoPz|vwS*q*JGF&!P89DfnQ9iREYMt9`9fy*W|&*ZVY_E9H+Dkj(`=4|3K!w`Sw2>C zUt{&!^zYtBcRa2p;@sSi_v-7C`Q$BY!brHnZk4P=SBIIH@k(OE!C%ZqPm%7tjrHOD z&dzRv&)cge`b)eY-^YyOwf5r`?ARGA4~J$f$9Wj(KFK)mpZg^PA@wL|D9iY+z@C}F zUg`*bVaV?A2TynF%WazydpK4MOrC18C}~@9)S=qOgdtHE z^X*HE_q$}jSCO25{nGoRZ=^aCjZ37(jeF+{xsu)`e<|wqXJ{oaLacV-qu()CK6)84n6L96LyRMY9jEf+yWVG z@Lo|nLl?9S1?F>`HN_2WK%=?|oLZ+iToYDopvz$%s7b8F74XE(W9 z*BErC`UyA7q#C@gpASju=}o>$Esd#2XZ(Hed9iiSIxc8fKUX1*^TPG~UY; zKIwZ|!F|?K0;1pV-Zky!?J9hOMohtWFQQihD|y`JU3t~ZR-f=Uqe}h3nZ;}$3a>gh zMc7pq)=shQF)eh=#34!SyecVU?cxZJAi8O6OR_3yY|Ey1b2w)9gW8St-rjFsxBbl| zOYYG>>VK1LV;-GGn}Ut77}L>wwtCK&4BzhM3dR+JOykFa@oYAzk>(!EAhCpEG{FtV~JqgDJE3ORz;SI>0kM=+P4proQGaxvgN($doOiIddah)4MPLG zbCh8owL_ScPK>Kl_MMB9bV5>LQoVlSt)-DqY;U`%#9Y_UpX@~{CZV{H@ba_ALveer z+h*`vrCPr#%5^DJ}I{1i|_vYHEedpyuhuwt{MMHwi9rw%Ej1c@hiOoCl z{F=2A`91Cy#qeql1!aa6^VM|-L@>q!SLfQK4i-;IJT1ZUdXhc!a_dn>1zqyjxMXsJ zR{Q7zb!t)R`ehF(+AyS+awJ0Sxa0GJI}eCglODa$xv|rF{3S~*E-G+g{StwPQiwvQ zy>5`IH)9y-825q>>0JVon=HAt-GSLwqJt&gz6Y9(xwxK@Xu?|;S{a1OCUm)$h|`#z z{4R&8W5w#VQgt%13T(T)O7jdWTXEK?eL{3^p8hgtp}fJHIyNrUx5ph*85S&zsG4=_ zDuM0yva6_cbC=E;#pY<4Z!162=4& z$Bdfdo4QwTlR4oqn_V0e(UrqdQ%hc^D*?k|4@1|+HE^(EbDEGGkw1u?N8;hpX?(~` zVQz}pbkh(~Z&(%QFpY2!D|w^}(eK`L>^X{F3?gku*E8)1c-b4*Qf}_t_uJIUCh_Jm zBi&=s%uf$~@AtrvuG@7+Oi$51_x;o9+cGu*m$R=n<>e@CT_{J#R!vNaAFnlV#JeZj zY#VTCG`z?5i0svQbgjW94O1gNFRvC_K56TKVVhDuyD93u+r( zUra=z42`H`iSyK7Kef^gR=A_yQF-@}DsoJ|w~lOY9=Kk`CY4FU=Fa`Gm}R+20aJFa z-EHLwy26clp7rZHjVjMOcRh^{d!bN?aN-6!SbbrFzuZ(XCy(Mz)8pJfnY0`bL&@7&ymN%OJ zPOGDJ$eqji$$Y~jw2MR!M-7)!#yA5^F?&_g9Yn}mlNWoD-w~4berZ+fkn7Z5Ovpz@ zN=jnYsya9$8g+X|rq02f5h($LQk?)#A8pjbY;f@XEr9GBqNBsEqNixJxO zBkK?DGmp!Sg&&vPDHHBBGJXwZ74Gwv7u7sqL-dVIMZ zq=R|Hwh?LK6-pG51^Xr&bPXR@SeYEC`3y@Uhy_MmvE!lWgLRUOu>&=+w*q zb-{7^<5Z)5FyGv*q^uh=qTK|ETNz#{Y(eT`rRZ8c6Y}ns`Mcx3#4CRFR#zg*5p@OW zibj!!Jf>HVJJ@nMFWR*;6Om}kq$+2Ir7P{$E`7?1Iw!)Y8QL7jZcIgLBui@I7IA9= ze>%yvM^dy*+9yjU*cBToXJX8gW9F(gTT($n&M;cNtK6IyCRrEPp#{sJtCE}hWDthSz4VsHEWc$1P{>9%XST!Y7lgp!q^^@LB1Av4E%@{d^k2a-SdV`F-Mno5!0kM$+!JE}=!QAlF8i z6=jujr9@~4TfSZhK&r(*qE2nXt&;#E_gI(E1aTSy(oOmyFVgr%iiAoVDr;kInfU|?c#id z=d6NDJ_~#kS=wotm~vZRP-spwfaT^J);Zs`2z4}Qw@f}+o zIum&V3ZoNj6>XI<-%~x;-m|8(Pp5Ta?SqQS9+PHG;*0c&4V0H=fVu@JcD<-pE1qD%M;TUvGMtHrGI*h0&vI z=u4sVG>MmM^ZWGodhPh$gNt;!JUo9)iN_D_K{t9xk5#hu zwD*OL5i;k9{7|O)dKfscO2(E(G2_Hs<*%0% zjboa2Z0Ikf@H84-$U(v55DCu-Hj0TMS`Ox=)_dGIyeM%tgo;A~N7^)$st>QCPvcb% z30Ed3n-7EG1ihSMn#Q89I^&+62yeUhMk>z7x{CSfjaMXYS|sb!y02}@k~XGX?WQuG zx^b0_F+`r%TzbCN;O_0byE-vY_jo<&@l?@f!&)uL@&1;l`U_59_i88i0@vEFyUWun z*`hLLV?NCnF{{*?s(RwMkMKiz*$_GvxDcY=H??kxPL@v1uPePoI7CH#-j2$pK&gSauyZnT2>AwcvXJMVk$3U&_xZ%I!@bwrU(p-rqVL{IDdV+ae3B^1rFuC< zJ>JG})z=W8?~pF=k;wF%Rn zrNh4%O&{5tPqqFGp5l^xWm(TNGd6SA=$6ASCSN~cGsc5ty*u?N44$T<)X7%F zm~BGaB3J|A%`X}?h^#Zt-4&DNBiSl3-)oswERMD2?~-BlKFRrrQ=@hsv~^RAeaD_3 zg5=w!Lr8eN7OBW=l^7+g@QuL2ja*|Y4y`2b=I2{$olXZ6ka1wCVS4SvY$S0TIn=uTQBUvWt|)*9k?mjzHZ<KFVk(gh=Vi#N2nu9IT25+xw7 z7?Sw;5Fn8|%-La1XRSbbjcf zu0HyQPbKd@A6E9=VCWD*YG1$aky5Sew00XM`rAS!U+(=9^;`FsuxM6S@>_N;N0-V1 zm2mCFW!x^9e?9}GcnH65R>RyM2V{S(|Cg9PRjG{IdeKMD0-fxe_Qs$4ci>Q zw7b5L=H$F@z_&URrXqTdm2K(CK;Men7mV-HdbPv&+%*~_l$saCIMFNKqUl+?55CCL zZWP7~ry762A+mPsy+_qEyC4D4n`BX{POl{d)l7L%Fjm*19k^5bnif%&GX*g1muQU} zr*Y4u6poLidkkArMlhd8kEzqG@KEq!Na-lJsJs}OKayW=%s-i)aY&_CP$lu5T{PUa zm)g}oj?0@DCncd^nbL(y?wNpud~9lOkwz@P@{5IQ-R5*W3M1vU5=`VCysY|bwg=j1 zB58fsIjWvv^%5+1Ik~-$v)EO^Ei^#ZE8)Kx^a-QDow3vHxyRj&qo%#5@8a-~R$`bc z=bl!3d)aZT$+=E$vWgA;j1QVl|i0(-#q# z&cxDl_r|$xUc<>4y)a3H6J$O(e|c|Oe+U<<8m0<23e%t*3=B`ehBG%gt2hQ8m zq+HHDSCpTBUZ|z0O{bx8-0M=%_9w}>UWPG8v@_W#Q?ma3O~@@XvaV;Nas$gzE*kEB zljq20y^JhFCJ5N?)ZItKP!FIdP7zpj#q<~!&l(lE|E5}tr=zm|<8e}AuibR=d((}c zWiH+_H9nQ$H=Q;(L9?{t^;`;%ySv7yEgW+bbvtcShqWwGVyx&$U#Eon;vO$fJsi+~ zjWb+wnNCDrazdKCLK2@=JWPUDE~|(8(a7#+jl&CPThG|DQJS&fDyV1U;_2tbK5LIhVaEa^>( zVMKGYlV=bTuN3HKRRs_TaKi!u8bUa*ZdHkx$-WQ#qyw}!(qAAHz;8H!FB}jfYZC)o zRzm}4NHhd-KeYEm!-y5cAz&0}H25Zw{6Yhnv<)8(xHQz-#n^-m5-c6B1`JYNc=9}2 zgEj5S{d84%JCb*EKo^n);M4vBqX2*y0b}8q_7m*aU$L4);;Iy8m(fuUBmR8C!v&?n zY8@&qssa;pCE&y`1r)9-3S8X3@SLn|ArTRD8?2THh7o<Y6AdRzdo0K*$8svGh(=i z))t0PPDGgFTUi2tSmJBJK<8`(VN8qmPgmu6i=J8v{I`~X%l?WO3g9<=@PU4Aus~Yn z7tEYU1$Zvh#Dww+@RZp=Cjvbo8^Z;a7**}S0TfVnT0HP2`(+gftd0b(RgNYG#|>}V5^89io)wZpa4aP_4lUwgUS%Y4wy|z8&fX+X4{Z6F}`0cNr;IB^oXG)|kkX-6&U@d2BY~s%L8#^XP51+Y#8+V1%=?P~ zNP@S2PC#5zN)EKm3E4UPR3g557pz1ikudlP+3$gTQ8hDT`x0myLgzq*Q9+A%(1!x} zK3Du>CBQ_W2F{-}+}OT?;RD4WX~1^~*Do3%hh(V#F%3flE2!>@iV+p;6j_gxr?(j- ziNE6Vb~ROHATVoq4Db^8MFOP7m;a8$DSqKEKM54ONAS7yX=Thw0yByG03SXmF7LGe zD?vb9;JQXq{X`Yha#$u;@gn4HGT*w|0}-ME)B?H+#l%AQkD0(z1nI^DMW82F=sXFL zTa&OnK#_{wk9O?=pvVEB2sacDmOK9y54dWuh?F!wXPD z1d0WU(I2z8X5naJ1k}L=`gNH0OJJG@8sE-N{@|kqpN}O^(401)N(!Kg5EP##^MAz$ zTAd$khTQhRs?a`%Wupniib%`r0(w9lO`tcq2F1qF>W|q-1Bcn5N4wwqII`6oP~=ps zov^yF4$BJUGbO8a1s_182|yzWC{_pd{{vQVl?o2t2P=hyFA5V!!#AF)CfCoJMgun? zBU6IP9Z~~*4Cq4ve8-&rm=mB92Pfa@S_$wPE&`M8Foo}6K3i;(FucT^o~VxVA7^d> zItc>t4eh~RpZ^IjXtflnu=xO1T#YBJHVt6a0(r1n$0hnMFpKj5yeI~x7G>an!|Jd3 z)Zeu`biD(xK1;#y89B^8-`4}!FaTOX_XCIb{v{)5g?_ZWwRb0c`i9~25wy8_OaZjK zeIS&e{Z{_qU-J1;hy!YN@q<;t<3q4YEr+nIT!8+-IncJ=2w3@=1Erxvq5Q@h{$I1w za5OQ2ahp*=H>fKJW;+5CDpnAK32zV~_bf&ED!^^EfKnD0 zJ^}J*0i2*m6PIWIiVw6pKcWZkECP65c5fLz8;SxY%nv}1XaTf*=t*+(^FL+-ce(I` z$RR!=n1~m)I84;!0dDd)F!QVg!nm9O0YM5XYK~L>n2550g{#TweKm_Lx)R{6wBQq3 zj(V`PK;Dw{Ek17pcbxZ-Oap`u6)=E6 z4{Xc1{{vE-FfHG;oe(6J)ba^0p>MFfKw?ciBXW}l#8Lyqg6<&gi~lFQ;Hq^+?<`m? z_RA75kp*Ivek4v*47|l(1ujy6Zg7Oa*4IDX6Zo+ZRkd?GeKL8b?}QD#G<-JBpRj$@ z0F5XCji47mRF(gN4P0eLt&zaWoR5Le<A+txv;1oO zpcQjh(?6#JKGF<1vcE<<2Uej{>*P751v^g!1up7)L9e&K{8fVJ_kDv)&3_1QX5wsT z@BG{1^oLVYhfj7I`+VTjfW9=E8n7Ppp#Z*IUH=u0A6H02N`ml%i%BCoSen{S_*|gx zn1SRH`Th^NoHkfp$;ANEDB6Kf1NvSTNE-9Ae@H_E-YqTgq}jB=1arzFx(2tKgD&n4 z$%%9IUvoNTep=6V62&oQu)JXF97FPI*gTcj&-!)0cP7*logB-+f+nT64lIPjBrGs& z%~VL>wNL*H7@pNX-Wd&8*Mq@L&q~2~1Gdy5B>2m3e+2&X%{x>`EZZ;418+bGIvT*> z>T0kgU`ql*lEB&jGZLT|xKIq7Qj3r9h7tMCHh~#%wZSrgE#n8tVCp|G_*u;A_x%WW zVK&ekw7`!iz_jbIz_4|*Ab}T;PdEK1F!YEq9KH=w{LY$!#*T0GzK?xlxUk|dkVfX|X*$@^Ow!Q}>Fbmp01BSL2?Z{yUu$NN)HQ3IhU|3k# zN(+#%#F(eS{;0bEW$7~_g*YIjfV$>j$faReNZ3qwNJuQ~{{#tbqstvi5a3mw7BJxC zAuJ$lJ~<>HAE6L@Gh zKekl|fircSfWM4~1cyJ_BZQjly(EMS4l}RYtiAvqp$FOUz|RLfvM|ntO=yEO^TR1f zKhxWw{Mziz2zt#gaTXhlNEagqg9w{G0g1Rq{`a=Q4Pc@0mKgw1>>>^r@tz*6jj-th zkcj#ee~;@qq$OC_hXn^G)3$R0l F@PFxb4{87a literal 0 HcmV?d00001 From 4b4d25b691ac258fe96db619a6302582e5d20552 Mon Sep 17 00:00:00 2001 From: zhengchao Date: Mon, 4 May 2020 17:46:09 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E5=90=88=E5=B9=B6plugin=E5=92=8Cip=5Fplugi?= =?UTF-8?q?n=E7=9A=84=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91=EF=BC=8C?= =?UTF-8?q?=E6=8A=BD=E8=B1=A1=E4=B8=BAMaat=5Fex=5Fdata.h/cpp=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- inc/Maat_rule.h | 11 +- src/CMakeLists.txt | 2 +- src/entry/Maat_api.cpp | 72 +++-- src/entry/Maat_ex_data.cpp | 193 +++++++++++++ src/entry/Maat_rule.cpp | 118 ++------ src/entry/Maat_stat.cpp | 2 +- src/entry/Maat_table.cpp | 91 ++++-- src/entry/Maat_table_runtime.cpp | 397 ++++++++++++-------------- src/inc_internal/Maat_ex_data.h | 45 +++ src/inc_internal/Maat_rule_internal.h | 3 - src/inc_internal/Maat_table.h | 20 +- src/inc_internal/Maat_table_runtime.h | 21 +- test/maat_json.json | 9 + test/table_info.conf | 3 +- test/test_maatframe.cpp | 83 +++++- 15 files changed, 707 insertions(+), 363 deletions(-) create mode 100644 src/entry/Maat_ex_data.cpp create mode 100644 src/inc_internal/Maat_ex_data.h diff --git a/inc/Maat_rule.h b/inc/Maat_rule.h index ca557f3..cea3938 100644 --- a/inc/Maat_rule.h +++ b/inc/Maat_rule.h @@ -293,7 +293,6 @@ typedef void Maat_plugin_EX_free_func_t(int table_id, MAAT_PLUGIN_EX_DATA* ad, l typedef void Maat_plugin_EX_dup_func_t(int table_id, MAAT_PLUGIN_EX_DATA *to, MAAT_PLUGIN_EX_DATA *from, long argl, void *argp); typedef int Maat_plugin_EX_key2index_func_t(const char* key); -//For IP plugin, key2index_func MUST be NULL. int Maat_plugin_EX_register(Maat_feather_t feather, int table_id, Maat_plugin_EX_new_func_t* new_func, Maat_plugin_EX_free_func_t* free_func, @@ -302,6 +301,14 @@ int Maat_plugin_EX_register(Maat_feather_t feather, int table_id, long argl, void *argp); //Data is duplicated by dup_func of Maat_plugin_EX_register, caller is responsible to FREE the data. MAAT_PLUGIN_EX_DATA Maat_plugin_get_EX_data(Maat_feather_t feather, int table_id, const char* key); + + +int Maat_ip_plugin_EX_register(Maat_feather_t feather, int table_id, + Maat_plugin_EX_new_func_t* new_func, + Maat_plugin_EX_free_func_t* free_func, + Maat_plugin_EX_dup_func_t* dup_func, + long argl, void *argp); + struct ip_address { int ip_type; //4: IPv4, 6: IPv6 @@ -312,7 +319,7 @@ struct ip_address }; }; -MAAT_PLUGIN_EX_DATA Maat_IP_plugin_get_EX_data(Maat_feather_t feather, int table_id, const struct ip_address* ip); +int Maat_ip_plugin_get_EX_data(Maat_feather_t feather, int table_id, const struct ip_address* ip, MAAT_PLUGIN_EX_DATA* ex_data_array, size_t n_ex_data); enum MAAT_RULE_OPT diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index db42f65..9ebce39 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,7 +8,7 @@ set(MAAT_FRAME_VERSION ${MAAT_FRAME_MAJOR_VERSION}.${MAAT_FRAME_MINOR_VERSION}.$ message(STATUS "Maat Frame, Version: ${MAAT_FRAME_VERSION}") add_definitions(-fPIC) -set(MAAT_SRC entry/cJSON.c entry/config_monitor.cpp entry/dynamic_array.cpp entry/gram_index_engine.c entry/interval_index.c entry/json2iris.cpp entry/Maat_utils.cpp entry/Maat_api.cpp entry/Maat_command.cpp entry/Maat_rule.cpp entry/Maat_table.cpp entry/Maat_table_runtime.cpp entry/Maat_stat.cpp entry/map_str2int.cpp entry/rbtree.c entry/stream_fuzzy_hash.c entry/bool_matcher.cpp) +set(MAAT_SRC entry/cJSON.c entry/config_monitor.cpp entry/dynamic_array.cpp entry/gram_index_engine.c entry/interval_index.c entry/json2iris.cpp entry/Maat_utils.cpp entry/Maat_api.cpp entry/Maat_command.cpp entry/Maat_rule.cpp entry/Maat_table.cpp entry/Maat_table_runtime.cpp entry/Maat_stat.cpp entry/map_str2int.cpp entry/rbtree.c entry/stream_fuzzy_hash.c entry/bool_matcher.cpp entry/Maat_ex_data.cpp) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../inc/) include_directories(/opt/MESA/include/MESA/) diff --git a/src/entry/Maat_api.cpp b/src/entry/Maat_api.cpp index cd031a2..b25867a 100644 --- a/src/entry/Maat_api.cpp +++ b/src/entry/Maat_api.cpp @@ -1331,38 +1331,24 @@ int Maat_plugin_EX_register(Maat_feather_t feather, int table_id, { struct _Maat_feather_t* _feather=(_Maat_feather_t*)feather; - int idx=-1; + int ret=0; + struct Maat_table_schema *table_schema=Maat_table_get_by_id_raw(_feather->table_mgr, table_id); - if(new_func==NULL || free_func==NULL || dup_func==NULL ) - { - assert(0); - MESA_handle_runtime_log(_feather->logger, RLOG_LV_FATAL, maat_module, "%s failed: invalid paramter", __FUNCTION__); - return -1; - } pthread_mutex_lock(&(_feather->background_update_mutex)); + ret=Maat_table_plugin_EX_data_schema_set(table_schema, new_func, free_func, dup_func, key2index_func,argl, argp, _feather->logger); - idx=Maat_table_plugin_new_ex_index(_feather->table_mgr, table_id, - new_func, - free_func, - dup_func, - key2index_func, - argl, argp); - if(idx<0) + if(ret<0) { pthread_mutex_unlock(&(_feather->background_update_mutex)); return -1; } - struct Maat_table_schema *table_desc=Maat_table_get_scan_by_id(_feather->table_mgr, table_id, TABLE_TYPE_PLUGIN, NULL); struct Maat_table_runtime* table_rt=NULL; - - if(_feather->scanner!=NULL) { table_rt=Maat_table_runtime_get(_feather->scanner->table_rt_mgr, table_id); - Maat_table_runtime_plugin_new_ex_idx(table_rt, table_desc, _feather->logger); + Maat_table_runtime_plugin_commit_ex_schema(table_rt, table_schema, _feather->logger); } pthread_mutex_unlock(&(_feather->background_update_mutex)); - return 0; } MAAT_PLUGIN_EX_DATA Maat_plugin_get_EX_data(Maat_feather_t feather, int table_id, const char* key) @@ -1380,24 +1366,62 @@ MAAT_PLUGIN_EX_DATA Maat_plugin_get_EX_data(Maat_feather_t feather, int table_id exdata=Maat_table_runtime_plugin_get_ex_data(table_rt, table_desc, key); return exdata; } -int Maat_IP_plugin_get_EX_data(Maat_feather_t feather, int table_id, const struct ip_address* ip, MAAT_PLUGIN_EX_DATA* ex_data_array, size_t n_ex_data) +int Maat_ip_plugin_EX_register(Maat_feather_t feather, int table_id, + Maat_plugin_EX_new_func_t* new_func, + Maat_plugin_EX_free_func_t* free_func, + Maat_plugin_EX_dup_func_t* dup_func, + long argl, void *argp) + { struct _Maat_feather_t* _feather=(_Maat_feather_t*)feather; - struct Maat_table_schema *table_desc=NULL; + int ret=-1; + struct Maat_table_schema *table_schema=Maat_table_get_by_id_raw(_feather->table_mgr, table_id); + pthread_mutex_lock(&(_feather->background_update_mutex)); + ret=Maat_table_ip_plugin_EX_data_schema_set(table_schema, new_func, free_func, dup_func, NULL, argl, argp, _feather->logger); + + if(ret<0) + { + pthread_mutex_unlock(&(_feather->background_update_mutex)); + return -1; + } + + struct Maat_table_runtime* table_rt=NULL; + if(_feather->scanner!=NULL) + { + table_rt=Maat_table_runtime_get(_feather->scanner->table_rt_mgr, table_id); + Maat_table_runtime_ip_plugin_commit_ex_schema(table_rt, table_schema, _feather->logger); + } + pthread_mutex_unlock(&(_feather->background_update_mutex)); + + return 0; +} + +int Maat_ip_plugin_get_EX_data(Maat_feather_t feather, int table_id, const struct ip_address* ip, MAAT_PLUGIN_EX_DATA* ex_data_array, size_t n_ex_data) +{ + struct _Maat_feather_t* _feather=(_Maat_feather_t*)feather; + struct Maat_table_schema *table_schema=NULL; struct Maat_table_runtime *table_rt=NULL; int n_get=0; if(_feather->scanner==NULL) { return 0; } - table_desc=Maat_table_get_scan_by_id(_feather->table_mgr, table_id, TABLE_TYPE_PLUGIN, NULL); + table_schema=Maat_table_get_scan_by_id(_feather->table_mgr, table_id, TABLE_TYPE_PLUGIN, NULL); table_rt=Maat_table_runtime_get(_feather->scanner->table_rt_mgr, table_id); if(table_rt->table_type!=TABLE_TYPE_IP_PLUGIN) { return -1; } - - n_get=Maat_table_runtime_ip_plugin_get_N_ex_data(table_rt, table_desc, ip, ex_data_array, n_ex_data); + struct ip_data ip_data=*(const struct ip_data*)ip; + if(ip_data.type==IPv4) + { + ip_data.ipv4=ntohl(ip_data.ipv4); + } + else + { + ipv6_ntoh(ip_data.ipv6); + } + n_get=Maat_table_runtime_ip_plugin_get_N_ex_data(table_rt, table_schema, &ip_data, ex_data_array, n_ex_data); return n_get; } diff --git a/src/entry/Maat_ex_data.cpp b/src/entry/Maat_ex_data.cpp new file mode 100644 index 0000000..25f6bee --- /dev/null +++ b/src/entry/Maat_ex_data.cpp @@ -0,0 +1,193 @@ +#include "Maat_ex_data.h" +#include "Maat_table.h" +#include "Maat_utils.h" + +#include +#include + +void EX_data_container_free(void *data) +{ + struct EX_data_container* wrap_data=(struct EX_data_container*)data; + const struct EX_data_schema* ex_schema=wrap_data->rt->ex_schema; + ex_schema->free_func(wrap_data->rt->table_id, &(wrap_data->ex_data), ex_schema->argl, ex_schema->argp); + if(wrap_data->user_data && wrap_data->rt->user_data_free) + { + wrap_data->rt->user_data_free(wrap_data->user_data); + } + wrap_data->user_data=NULL; + wrap_data->rt=NULL; + free(wrap_data); + return; +} +static MESA_htable_handle EX_data_hash_new(long long estimate_size, Maat_plugin_EX_key2index_func_t * key2index) +{ + MESA_htable_handle key2ex_hash=NULL; + unsigned int slot_size=1; + while(estimate_size!=0) + { + estimate_size=estimate_size>>1; + slot_size*=2; + } + if(slot_size==1) + { + slot_size=4096; + } + + MESA_htable_create_args_t hargs; + memset(&hargs,0,sizeof(hargs)); + hargs.thread_safe=8; + hargs.hash_slot_size = slot_size; + hargs.max_elem_num = 0; + hargs.eliminate_type = HASH_ELIMINATE_ALGO_FIFO; + hargs.expire_time = 0; + hargs.key_comp = NULL; + hargs.key2index = NULL; //Not supported yet. + hargs.recursive = 1; + hargs.data_free = EX_data_container_free; + hargs.data_expire_with_condition = NULL; + key2ex_hash=MESA_htable_create(&hargs, sizeof(hargs)); + MESA_htable_print_crtl(key2ex_hash, 0); + return key2ex_hash; +} + +struct EX_data_rt* EX_data_rt_new(int table_id, long long estimate_size, Maat_plugin_EX_key2index_func_t * key2index, void (* user_data_free)(void *user_data)) +{ + struct EX_data_rt* p=ALLOC(struct EX_data_rt, 1); + p->key2ex_hash=EX_data_hash_new(estimate_size, key2index); + p->cache_rows=dynamic_array_create(4, 1024); + p->table_id=table_id; + p->user_data_free=user_data_free; + return p; +}; +void EX_data_rt_set_schema(struct EX_data_rt* p, const struct EX_data_schema* schema) +{ + p->ex_schema=schema; +} +void EX_data_rt_free(struct EX_data_rt* p) +{ + if(p->cache_rows) + { + dynamic_array_destroy(p->cache_rows, free); + p->cache_rows=NULL; + } + MESA_htable_destroy(p->key2ex_hash, NULL); + return; +} +void EX_data_rt_cache_row(struct EX_data_rt* p, const char* row) +{ + size_t len=strlen(row)+1; + char* row_copy=ALLOC(char, len); + memcpy(row_copy, row, len); + p->cache_size+=len; + dynamic_array_write(p->cache_rows, p->cache_row_num, row_copy); + p->cache_row_num++; + return; +} +const char* EX_data_rt_get_cached_row(struct EX_data_rt* p, int i) +{ + const char* row=NULL; + row=(const char*)dynamic_array_read(p->cache_rows, i); + return row; +} +void EX_data_rt_clear_row_cache(struct EX_data_rt* p) +{ + dynamic_array_destroy(p->cache_rows, free); + p->cache_rows=NULL; + p->cache_row_num=0; + p->cache_size=0; +} +int EX_data_rt_get_row_num(struct EX_data_rt* p) +{ + return p->cache_row_num; +} +struct EX_data_container* EX_data_rt_row2EX_data(struct EX_data_rt* ex_rt, + const char* row, const char* key, size_t key_len, + void* user_data, void* logger) +{ + + MAAT_RULE_EX_DATA ex_data=NULL; + int ret=0; + const struct EX_data_schema* ex_schema=ex_rt->ex_schema; + struct EX_data_container* ex_container=ALLOC(struct EX_data_container, 1); + ex_schema->new_func(ex_rt->table_id, key, row, &ex_data, + ex_schema->argl, ex_schema->argp); + ex_container->ex_data=ex_data; + ex_container->rt=ex_rt; + ex_container->user_data=user_data; + ret=MESA_htable_add(ex_rt->key2ex_hash, (unsigned char*)key, key_len, ex_container); + if(ret<0) + { + MESA_handle_runtime_log(logger, RLOG_LV_FATAL, maat_module, + "EX data add error: duplicated key %.*s of %s", + key_len, key, row); + EX_data_container_free(ex_container); + return NULL; + } + return ex_container; +} +int EX_data_rt_delete_by_row(struct EX_data_rt* ex_rt, const char* row, const char* key, size_t key_len, + void *logger) +{ + int ret=0; + ret=MESA_htable_del(ex_rt->key2ex_hash, (const unsigned char*)key, key_len, NULL); + if(ret<0) + { + MESA_handle_runtime_log(logger, RLOG_LV_FATAL, maat_module, + "EX data del error: no such key %.*s of %s", + key_len, key, row); + return -1; + } + return 0; +} + +MAAT_RULE_EX_DATA EX_data_rt_get_EX_data_by_key(struct EX_data_rt* ex_rt, const char* key, size_t key_len) +{ + struct EX_data_container* container=NULL; + MAAT_RULE_EX_DATA ex_data=NULL; + + if(!ex_rt->ex_schema) + { + assert(0); + return NULL; + } + container=(struct EX_data_container*)MESA_htable_search(ex_rt->key2ex_hash, + (const unsigned char*)key, strlen(key)); + if(container!=NULL) + { + ex_rt->ex_schema->dup_func(ex_rt->table_id, &(ex_data), &(container->ex_data), + ex_rt->ex_schema->argl, ex_rt->ex_schema->argp); + } + return ex_data; +} +MAAT_RULE_EX_DATA EX_data_rt_get_EX_data_by_container(struct EX_data_rt* ex_rt, struct EX_data_container* container) +{ + MAAT_RULE_EX_DATA ex_data=NULL; + ex_rt->ex_schema->dup_func(ex_rt->table_id, &(ex_data), &(container->ex_data), + ex_rt->ex_schema->argl, ex_rt->ex_schema->argp); + return ex_data; +} +struct key2EX_hash_walker +{ + EX_data_container_q* listed; + size_t count; +}; + +void walk_key2EX_hash(const uchar * key, uint size, void * data, void * user) +{ + struct key2EX_hash_walker *walker=(struct key2EX_hash_walker*)user; + struct EX_data_container* ex_container=(struct EX_data_container*)data; + TAILQ_INSERT_TAIL(walker->listed, ex_container, entries); + walker->count++; + return; +} +size_t EX_data_rt_list_all(struct EX_data_rt* ex_rt, EX_data_container_q* listed) +{ + size_t ex_data_cnt; + struct key2EX_hash_walker walker={listed, 0}; + TAILQ_INIT(listed); + MESA_htable_iterate(ex_rt->key2ex_hash, walk_key2EX_hash, &walker); + ex_data_cnt=(size_t)MESA_htable_get_elem_num(ex_rt->key2ex_hash); + assert(walker.count==ex_data_cnt); + return ex_data_cnt; +} + diff --git a/src/entry/Maat_rule.cpp b/src/entry/Maat_rule.cpp index 34c2eed..902fab7 100644 --- a/src/entry/Maat_rule.cpp +++ b/src/entry/Maat_rule.cpp @@ -3000,44 +3000,41 @@ void garbage_bury(MESA_lqueue_head garbage_q, int timeout, void *logger) q_cnt,bury_cnt); } } -void update_plugin_table(struct Maat_table_schema* table, const char* table_line, Maat_scanner* scanner, const struct rule_tag* tags, int n_tags, void* logger) +void update_plugin_table(struct Maat_table_schema* table_schema, const char* row, Maat_scanner* scanner, const struct rule_tag* tags, int n_tags, void* logger) { - int i=0, ret=1, matched_tag=1; - unsigned int len=strlen(table_line)+1; - struct plugin_table_schema* plugin_desc=&(table->plugin); - struct Maat_table_runtime* table_rt=Maat_table_runtime_get(scanner->table_rt_mgr, table->table_id); - char *p=NULL; + int ret=1, matched_tag=1; + struct plugin_table_schema* plugin_desc=&(table_schema->plugin); + struct Maat_table_runtime* table_rt=Maat_table_runtime_get(scanner->table_rt_mgr, table_schema->table_id); char* copy=NULL; - size_t is_valid_offset=0, valid_len=0; size_t accept_tag_offset=0, accept_tag_len=0; if(plugin_desc->rule_tag_column>0&&n_tags>0) { - ret=Maat_helper_read_column(table_line, plugin_desc->rule_tag_column, &accept_tag_offset, &accept_tag_len); + ret=Maat_helper_read_column(row, plugin_desc->rule_tag_column, &accept_tag_offset, &accept_tag_len); if(ret<0) { MESA_handle_runtime_log(logger,RLOG_LV_FATAL,maat_module , - "update error, could not locate tag in column %d of plugin table %s:%s", + "update error, could not locate tag in column %d of plugin table_schema %s:%s", plugin_desc->rule_tag_column, - table->table_name[table->updating_name], - table_line); - table->udpate_err_cnt++; + table_schema->table_name[table_schema->updating_name], + row); + table_schema->udpate_err_cnt++; return; } if(accept_tag_len>2) { copy=ALLOC(char, accept_tag_len+1); - memcpy(copy, table_line+accept_tag_offset, accept_tag_len); + memcpy(copy, row+accept_tag_offset, accept_tag_len); matched_tag=compare_accept_tag(copy, tags, n_tags); if(matched_tag<0) { MESA_handle_runtime_log(logger,RLOG_LV_FATAL,maat_module , - "update error,invalid tag format of plugin table %s:%s" - ,table->table_name[table->updating_name],table_line); - table->udpate_err_cnt++; + "update error,invalid tag format of plugin table_schema %s:%s" + ,table_schema->table_name[table_schema->updating_name],row); + table_schema->udpate_err_cnt++; } if(matched_tag==0) { - table->unmatch_tag_cnt++; + table_schema->unmatch_tag_cnt++; } free(copy); copy=NULL; @@ -3047,79 +3044,43 @@ void update_plugin_table(struct Maat_table_schema* table, const char* table_line return; } } - - table_rt->plugin.acc_line_num++; - if(plugin_desc->have_exdata || plugin_desc->cb_plug_cnt>0) - { - - if(plugin_desc->have_exdata) - { - ret=Maat_helper_read_column(table_line, plugin_desc->valid_flag_column, &is_valid_offset, &valid_len); - //thread safe is protected by background_update_mutex - if(atoi(table_line+is_valid_offset)==1) - { - plugin_EX_data_new(table, table_line, table_rt->plugin.key2ex_hash, logger); - } - else - { - plugin_EX_data_free(table_line, plugin_desc->key_column, table_rt->plugin.key2ex_hash, logger); - } - } - if(plugin_desc->cb_plug_cnt>0) - { - for(i=0;icb_plug_cnt;i++) - { - plugin_desc->cb_plug[i].update(table->table_id, table_line, plugin_desc->cb_plug[i].u_para); - } - } - - } - else - { - p=ALLOC(char, len); - memcpy(p,table_line,len); - table_rt->plugin.cache_size+=len; - dynamic_array_write(table_rt->plugin.cache_lines, table_rt->plugin.cache_line_num, p); - table_rt->plugin.cache_line_num++; - } + Maat_table_runtime_plugin_new_row(table_rt, table_schema, row, logger); } -void update_ip_plugin_table(struct Maat_table_schema* table,const char* table_line,Maat_scanner* scanner, const struct rule_tag* tags, int n_tags, void* logger) +void update_ip_plugin_table(struct Maat_table_schema* table_schema, const char* table_row, Maat_scanner* scanner, const struct rule_tag* tags, int n_tags, void* logger) { int ret=1, matched_tag=1; - struct ip_plugin_table_schema* ip_plugin_schema=&(table->ip_plugin); - struct Maat_table_runtime* table_rt=Maat_table_runtime_get(scanner->table_rt_mgr, table->table_id); - struct ip_plugin_runtime* ip_plugin_rt=&(table_rt->ip_plugin); + struct ip_plugin_table_schema* ip_plugin_schema=&(table_schema->ip_plugin); + struct Maat_table_runtime* table_rt=Maat_table_runtime_get(scanner->table_rt_mgr, table_schema->table_id); char* copy=NULL; - size_t is_valid_offset=0, valid_len=0; size_t accept_tag_offset=0, accept_tag_len=0; if(ip_plugin_schema->rule_tag_column>0&&n_tags>0) { - ret=Maat_helper_read_column(table_line, ip_plugin_schema->rule_tag_column, &accept_tag_offset, &accept_tag_len); + ret=Maat_helper_read_column(table_row, ip_plugin_schema->rule_tag_column, &accept_tag_offset, &accept_tag_len); if(ret<0) { MESA_handle_runtime_log(logger,RLOG_LV_FATAL,maat_module , - "update error, could not locate tag in column %d of plugin table %s:%s", + "update error, could not locate tag in column %d of plugin table_schema %s:%s", ip_plugin_schema->rule_tag_column, - table->table_name[table->updating_name], - table_line); - table->udpate_err_cnt++; + table_schema->table_name[table_schema->updating_name], + table_row); + table_schema->udpate_err_cnt++; return; } if(accept_tag_len>2) { copy=ALLOC(char, accept_tag_len+1); - memcpy(copy, table_line+accept_tag_offset, accept_tag_len); + memcpy(copy, table_row+accept_tag_offset, accept_tag_len); matched_tag=compare_accept_tag(copy, tags, n_tags); if(matched_tag<0) { MESA_handle_runtime_log(logger, RLOG_LV_FATAL, maat_module, - "update error, invalid tag format of ip_plugin table %s:%s", - table->table_name[table->updating_name], table_line); - table->udpate_err_cnt++; + "update error, invalid tag format of ip_plugin table_schema %s:%s", + table_schema->table_name[table_schema->updating_name], table_row); + table_schema->udpate_err_cnt++; } if(matched_tag==0) { - table->unmatch_tag_cnt++; + table_schema->unmatch_tag_cnt++; } free(copy); copy=NULL; @@ -3129,25 +3090,8 @@ void update_ip_plugin_table(struct Maat_table_schema* table,const char* table_li return; } } - ret=Maat_helper_read_column(table_line, ip_plugin_schema->valid_flag_column, &is_valid_offset, &valid_len); - //thread safe is protected by background_update_mutex - if(atoi(table_line+is_valid_offset)==1) - { - ret=plugin_EX_data_new(table, table_line, ip_plugin_rt->rowid2ex_hash, logger); - if(ret==0) - { - ip_plugin_rt->row_num++; - } - } - else - { - ret=plugin_EX_data_free(table_line, ip_plugin_schema->row_id_column, ip_plugin_rt->rowid2ex_hash, logger); - if(ret==0) - { - ip_plugin_rt->row_num--; - } - - } + Maat_table_runtime_ip_plugin_new_row(table_rt, table_schema, table_row, logger); + return; } void vector_print(igraph_vector_t *v) { @@ -3293,7 +3237,7 @@ void do_scanner_update(struct Maat_scanner* scanner, MESA_lqueue_head garbage_q, } break; case TABLE_TYPE_IP_PLUGIN: - Maat_table_runtime_rebuild_ip_matcher(table_rt); + Maat_table_runtime_ip_plugin_rebuild_ip_matcher(table_rt); old_ip_matcher=Maat_table_runtime_dettach_old_ip_matcher(table_rt); garbage_bagging(GARBAGE_IP_MATCHER, old_ip_matcher, garbage_q); break; diff --git a/src/entry/Maat_stat.cpp b/src/entry/Maat_stat.cpp index f26d1d9..c283618 100644 --- a/src/entry/Maat_stat.cpp +++ b/src/entry/Maat_stat.cpp @@ -219,7 +219,7 @@ void maat_stat_output(struct _Maat_feather_t* feather) switch(p_table->table_type) { case TABLE_TYPE_PLUGIN: - plugin_cache_num+=table_rt->plugin.cache_line_num; + plugin_cache_num+=table_rt->plugin.ex_data_rt->cache_row_num; plugin_acc_num+=table_rt->plugin.acc_line_num; break; case TABLE_TYPE_GROUP: diff --git a/src/entry/Maat_table.cpp b/src/entry/Maat_table.cpp index 3771bef..e73ed79 100644 --- a/src/entry/Maat_table.cpp +++ b/src/entry/Maat_table.cpp @@ -285,11 +285,11 @@ int read_ip_plugin_table_schema(const char* line, struct Maat_table_schema* p) free(copy_line); if(read_cnt<5) { - return 0; + return -1; } else { - return -1; + return 0; } error_out: free(copy_line); @@ -869,32 +869,89 @@ int Maat_table_new_compile_rule_ex_index(struct Maat_table_manager* table_mgr, c return idx; } -int Maat_table_plugin_new_ex_index(struct Maat_table_manager* table_mgr, int table_id, +void Maat_table_EX_data_schema_set(struct EX_data_schema* ex_schema, Maat_plugin_EX_new_func_t* new_func, Maat_plugin_EX_free_func_t* free_func, Maat_plugin_EX_dup_func_t* dup_func, Maat_plugin_EX_key2index_func_t* key2index_func, long argl, void *argp) - { - struct Maat_table_schema *table_desc=NULL;; - table_desc=Maat_table_get_scan_by_id(table_mgr, table_id, TABLE_TYPE_PLUGIN, NULL); - struct plugin_table_schema* plugin_desc=&(table_desc->plugin); - - if(plugin_desc->have_exdata - || plugin_desc->key_column==0 || plugin_desc->valid_flag_column==0) + ex_schema->new_func=new_func; + ex_schema->free_func=free_func; + ex_schema->dup_func=dup_func; + ex_schema->key2index_func=key2index_func;//Set but not used. + ex_schema->argl=argl; + ex_schema->argp=argp; + return; +} +int Maat_table_plugin_EX_data_schema_set(struct Maat_table_schema *table_schema, + Maat_plugin_EX_new_func_t* new_func, + Maat_plugin_EX_free_func_t* free_func, + Maat_plugin_EX_dup_func_t* dup_func, + Maat_plugin_EX_key2index_func_t* key2index_func, + long argl, void *argp, + void* logger) +{ + if(new_func==NULL || free_func==NULL || dup_func==NULL ) { + assert(0); + MESA_handle_runtime_log(logger, RLOG_LV_FATAL, maat_module, "%s failed: invalid paramter", __FUNCTION__); return -1; } - plugin_desc->ex_desc.new_func=new_func; - plugin_desc->ex_desc.free_func=free_func; - plugin_desc->ex_desc.dup_func=dup_func; - plugin_desc->ex_desc.key2index_func=key2index_func;//Set but not used. - plugin_desc->ex_desc.argl=argl; - plugin_desc->ex_desc.argp=argp; - plugin_desc->have_exdata=1; + if(table_schema->table_type!=TABLE_TYPE_PLUGIN) + { + assert(0); + MESA_handle_runtime_log(logger, RLOG_LV_FATAL, maat_module, "Error: %s, Regist target is not a plugin table.", __FUNCTION__); + return -1; + } + if(table_schema->plugin.have_exdata) + { + assert(0); + MESA_handle_runtime_log(logger, RLOG_LV_FATAL, maat_module, "Error: %s, EX data already registed.", __FUNCTION__); + return -1; + } + if(table_schema->plugin.key_column==0 || table_schema->plugin.valid_flag_column==0) + { + assert(0); + MESA_handle_runtime_log(logger, RLOG_LV_FATAL, maat_module, "Error: %s, not enough schema information.", __FUNCTION__); + return -1; + } + Maat_table_EX_data_schema_set(&table_schema->plugin.ex_schema, + new_func, free_func, dup_func, key2index_func, argl, argp); + table_schema->plugin.have_exdata=1; return 0; } +int Maat_table_ip_plugin_EX_data_schema_set(struct Maat_table_schema *table_schema, + Maat_plugin_EX_new_func_t* new_func, + Maat_plugin_EX_free_func_t* free_func, + Maat_plugin_EX_dup_func_t* dup_func, + Maat_plugin_EX_key2index_func_t* key2index_func, + long argl, void *argp, + void* logger) +{ + if(new_func==NULL || free_func==NULL || dup_func==NULL ) + { + assert(0); + MESA_handle_runtime_log(logger, RLOG_LV_FATAL, maat_module, "%s failed: invalid paramter", __FUNCTION__); + return -1; + } + if(table_schema->table_type!=TABLE_TYPE_IP_PLUGIN) + { + assert(0); + MESA_handle_runtime_log(logger, RLOG_LV_FATAL, maat_module, "Error: %s, target table is not a ip_plugin table.", __FUNCTION__); + return -1; + } + if(table_schema->ip_plugin.have_exdata) + { + assert(0); + MESA_handle_runtime_log(logger, RLOG_LV_FATAL, maat_module, "Error: %s, EX data already registed.", __FUNCTION__); + return -1; + } + Maat_table_EX_data_schema_set(&table_schema->ip_plugin.ex_schema, + new_func, free_func, dup_func, key2index_func, argl, argp); + table_schema->ip_plugin.have_exdata=1; + return 0; +} void Maat_table_manager_all_plugin_cb_start(struct Maat_table_manager* table_mgr, int update_type) { table_mgr->active_plugin_table_num=0; diff --git a/src/entry/Maat_table_runtime.cpp b/src/entry/Maat_table_runtime.cpp index ede0861..accdc83 100644 --- a/src/entry/Maat_table_runtime.cpp +++ b/src/entry/Maat_table_runtime.cpp @@ -7,96 +7,52 @@ #include #include -struct wrap_plugin_EX_data -{ - MAAT_RULE_EX_DATA exdata; - int table_id; - const struct plugin_table_ex_data_schema* ex_desc; - struct ip_rule range_rule;//for ip_plugin - TAILQ_ENTRY(wrap_plugin_EX_data) entries; -}; -void wrap_plugin_EX_data_free(void *data) -{ - struct wrap_plugin_EX_data* wrap_data=(struct wrap_plugin_EX_data*)data; - wrap_data->ex_desc->free_func(wrap_data->table_id, &(wrap_data->exdata), wrap_data->ex_desc->argl, wrap_data->ex_desc->argp); - wrap_data->ex_desc=NULL; - wrap_data->table_id=-1; - free(wrap_data); - return; -} -MESA_htable_handle wrap_plugin_EX_hash_new(long long estimate_size, Maat_plugin_EX_key2index_func_t * key2index) -{ - MESA_htable_handle key2ex_hash=NULL; - unsigned int slot_size=1; - while(estimate_size!=0) - { - estimate_size=estimate_size>>1; - slot_size*=2; - } - if(slot_size==1) - { - slot_size=4096; - } - MESA_htable_create_args_t hargs; - memset(&hargs,0,sizeof(hargs)); - hargs.thread_safe=8; - hargs.hash_slot_size = slot_size; - hargs.max_elem_num = 0; - hargs.eliminate_type = HASH_ELIMINATE_ALGO_FIFO; - hargs.expire_time = 0; - hargs.key_comp = NULL; - hargs.key2index = NULL; //Not supported yet. - hargs.recursive = 1; - hargs.data_free = wrap_plugin_EX_data_free; - hargs.data_expire_with_condition = NULL; - key2ex_hash=MESA_htable_create(&hargs, sizeof(hargs)); - MESA_htable_print_crtl(key2ex_hash, 0); - return key2ex_hash; -} - -int plugin_EX_data_free(const char* line, int key_column, +int plugin_EX_data_free(const char* row, int key_column, MESA_htable_handle key2ex_hash, void *logger) { size_t key_offset=0, key_len=0; int ret=0; - ret=get_column_pos(line, key_column, &key_offset, &key_len); + ret=get_column_pos(row, key_column, &key_offset, &key_len); if(ret<0) { MESA_handle_runtime_log(logger, RLOG_LV_FATAL, maat_module, "plugin/ip_plugin EX data del error: cannot find column %d of %s", - key_column, line); + key_column, row); return -1; } - ret=MESA_htable_del(key2ex_hash, (const unsigned char*)line+key_offset, key_len, NULL); + ret=MESA_htable_del(key2ex_hash, (const unsigned char*)row+key_offset, key_len, NULL); if(ret<0) { MESA_handle_runtime_log(logger, RLOG_LV_FATAL, maat_module, "plugin/ip_plugin EX data del error: no such key %.*s of %s", - key_len, line+key_offset, line); + key_len, row+key_offset, row); return -1; } return 0; } -int ip_plugin_line_read(const struct ip_plugin_table_schema* schema, const char* line, struct ip_rule* range_rule) +struct ip_rule* ip_plugin_row2ip_rule(const struct ip_plugin_table_schema* schema, const char* row) { + struct ip_rule* range_rule=ALLOC(struct ip_rule, 1); + int ret[4]={0}; size_t column_offset=0, column_len=0; char start_ip[128]={0}, end_ip[128]={0}; - ret[0]=get_column_pos(line, schema->row_id_column, &column_offset, &column_len); - range_rule->rule_id=atoi(line+column_offset); + ret[0]=get_column_pos(row, schema->row_id_column, &column_offset, &column_len); + range_rule->rule_id=atoi(row+column_offset); - ret[1]=get_column_pos(line, schema->ip_type_column, &column_offset, &column_len); - int ip_type=atoi(line+column_offset); + ret[1]=get_column_pos(row, schema->ip_type_column, &column_offset, &column_len); + int ip_type=atoi(row+column_offset); - ret[2]=get_column_pos(line, schema->start_ip_column, &column_offset, &column_len); - strncpy(start_ip, line+column_offset, MIN(column_len, sizeof(start_ip))); + ret[2]=get_column_pos(row, schema->start_ip_column, &column_offset, &column_len); + strncpy(start_ip, row+column_offset, MIN(column_len, sizeof(start_ip))); - ret[3]=get_column_pos(line, schema->end_ip_column, &column_offset, &column_len); - strncpy(end_ip, line+column_offset, MIN(column_len, sizeof(end_ip))); + ret[3]=get_column_pos(row, schema->end_ip_column, &column_offset, &column_len); + strncpy(end_ip, row+column_offset, MIN(column_len, sizeof(end_ip))); if(ret[0]<0||ret[1]<0||ret[2]<0||ret[3]<0) { - return -1; + free(range_rule); + return NULL; } if(ip_type==4) @@ -111,76 +67,17 @@ int ip_plugin_line_read(const struct ip_plugin_table_schema* schema, const char* } else { - return -1; + free(range_rule); + return NULL; } if(ret[0]<0) { - return -1; + free(range_rule); + return NULL; } - range_rule->rule_id=(unsigned int)atoi(line+column_offset); range_rule->user_tag=NULL; - return 0; + return range_rule; } -int plugin_EX_data_new(const struct Maat_table_schema* table_schema, - const char* line, - MESA_htable_handle key2ex_hash, void *logger) -{ - char* key=NULL; - size_t key_offset=0, key_len=0; - MAAT_RULE_EX_DATA exdata=NULL; - const struct plugin_table_ex_data_schema* ex_desc=NULL; - int key_column=-1; - struct wrap_plugin_EX_data* wrap_data=ALLOC(struct wrap_plugin_EX_data, 1); - switch(table_schema->table_type) - { - case TABLE_TYPE_PLUGIN: - ex_desc=&(table_schema->plugin.ex_desc); - key_column=table_schema->plugin.key_column; - break; - case TABLE_TYPE_IP_PLUGIN: - ex_desc=&(table_schema->ip_plugin.ex_desc); - key_column=table_schema->ip_plugin.row_id_column; - ip_plugin_line_read(&table_schema->ip_plugin, line, &(wrap_data->range_rule)); - wrap_data->range_rule.user_tag=wrap_data; - break; - default: - assert(0); - break; - } - - int ret=0; - ret=get_column_pos(line, key_column, &key_offset, &key_len); - if(ret<0) - { - MESA_handle_runtime_log(logger, RLOG_LV_FATAL, maat_module, - "plugin/ip_plugin EX data add error: cannot find column %d of %s", - key_column, line); - goto error_out; - } - key=ALLOC(char, key_len+1); - memcpy(key, line+key_offset, key_len); - ex_desc->new_func(table_schema->table_id, key, line, &exdata, - ex_desc->argl, ex_desc->argp); - wrap_data->exdata=exdata; - wrap_data->ex_desc=ex_desc; - wrap_data->table_id=table_schema->table_id; - ret=MESA_htable_add(key2ex_hash, (const unsigned char*)line+key_offset, key_len, wrap_data); - free(key); - if(ret<0) - { - MESA_handle_runtime_log(logger, RLOG_LV_FATAL, maat_module, - "plugin/ip_plugin EX data add error: duplicated key %.*s of %s", - key_len, line+key_offset, line); - wrap_plugin_EX_data_free(wrap_data); - return -1; - } - return 0; -error_out: - free(wrap_data); - free(key); - return -1; -} - struct Maat_table_runtime_manager { struct Maat_table_runtime** table_rt; @@ -217,28 +114,36 @@ static void destroy_digest_rule(GIE_digest_t*rule) return; } -static struct Maat_table_runtime* table_runtime_new(const struct Maat_table_schema* table_desc, int max_thread_num) +static struct Maat_table_runtime* table_runtime_new(const struct Maat_table_schema* table_schema, int max_thread_num) { struct Maat_table_runtime* table_rt= ALLOC(struct Maat_table_runtime, 1); - table_rt->table_type=table_desc->table_type; - switch(table_desc->table_type) + table_rt->table_type=table_schema->table_type; + switch(table_schema->table_type) { case TABLE_TYPE_DIGEST: case TABLE_TYPE_SIMILARITY: table_rt->similar.update_q=MESA_lqueue_create(0,0); break; case TABLE_TYPE_PLUGIN: - table_rt->plugin.cache_lines=dynamic_array_create(1, 1024); - if(table_desc->plugin.have_exdata) + table_rt->plugin.ex_data_rt=EX_data_rt_new(table_schema->table_id, + table_schema->plugin.estimate_size, + table_schema->plugin.ex_schema.key2index_func, + NULL); + if(table_schema->plugin.have_exdata) { - table_rt->plugin.key2ex_hash=wrap_plugin_EX_hash_new(table_desc->plugin.estimate_size, - table_desc->plugin.ex_desc.key2index_func); + EX_data_rt_set_schema(table_rt->plugin.ex_data_rt, &table_schema->plugin.ex_schema); } break; case TABLE_TYPE_IP_PLUGIN: - table_rt->ip_plugin.rowid2ex_hash=wrap_plugin_EX_hash_new(table_desc->ip_plugin.estimate_size, - NULL); + table_rt->ip_plugin.ex_data_rt=EX_data_rt_new(table_schema->table_id, + table_schema->plugin.estimate_size, + table_schema->plugin.ex_schema.key2index_func, + free); + if(table_schema->ip_plugin.have_exdata) + { + EX_data_rt_set_schema(table_rt->ip_plugin.ex_data_rt, &table_schema->ip_plugin.ex_schema); + } break; default: break; @@ -283,18 +188,14 @@ static void table_runtime_free(struct Maat_table_runtime* p) MESA_lqueue_destroy(p->similar.update_q, lqueue_destroy_cb, NULL); } break; - case TABLE_TYPE_PLUGIN: - dynamic_array_destroy(p->plugin.cache_lines, free); - p->plugin.cache_lines=NULL; - if(p->plugin.key2ex_hash!=NULL) - { - MESA_htable_destroy(p->plugin.key2ex_hash, NULL); - } - break; + case TABLE_TYPE_IP_PLUGIN: - MESA_htable_destroy(p->ip_plugin.rowid2ex_hash, NULL); ip_matcher_free(p->ip_plugin.ip_matcher); - p->ip_plugin.row_num=0; + EX_data_rt_free(p->ip_plugin.ex_data_rt); + assert(p->ip_plugin.old_ip_matcher==NULL); + break; + case TABLE_TYPE_PLUGIN: + EX_data_rt_free(p->plugin.ex_data_rt); break; default: break; @@ -349,81 +250,83 @@ struct Maat_table_runtime* Maat_table_runtime_get(struct Maat_table_runtime_mana long long Maat_table_runtime_plugin_cached_line_count(struct Maat_table_runtime* table_rt) { struct plugin_runtime* plugin_rt=&(table_rt->plugin); - return plugin_rt->cache_line_num; + return plugin_rt->ex_data_rt->cache_row_num; } const char* Maat_table_runtime_plugin_get_cached_line(struct Maat_table_runtime* table_rt, long long Nth_line) { const char* line=NULL; struct plugin_runtime* plugin_rt=&(table_rt->plugin); - line=(const char*)dynamic_array_read(plugin_rt->cache_lines, Nth_line); + line=(const char*)dynamic_array_read(plugin_rt->ex_data_rt->cache_rows, Nth_line); return line; } -MESA_htable_handle plugin_EX_htable_new(const struct Maat_table_schema* plugin_table, - struct dynamic_array_t* lines, size_t line_cnt, void* logger) +int Maat_table_runtime_plugin_commit_ex_schema(struct Maat_table_runtime* table_rt, struct Maat_table_schema* table_schema, void* logger) { - MESA_htable_handle key2ex_hash=NULL; - size_t i=0; - const char* line=NULL; - const struct plugin_table_schema* plugin_desc= &(plugin_table->plugin); + int i=0; + const char* row=NULL; + EX_data_rt_set_schema(table_rt->plugin.ex_data_rt, &table_schema->plugin.ex_schema); - key2ex_hash=wrap_plugin_EX_hash_new(plugin_desc->estimate_size, plugin_desc->ex_desc.key2index_func); - - for(i=0; i< line_cnt; i++) + for(i=0; iplugin.ex_data_rt); i++) { - line=(const char*)dynamic_array_read(lines, i); - plugin_EX_data_new(plugin_table, line, key2ex_hash, logger); + row=EX_data_rt_get_cached_row(table_rt->plugin.ex_data_rt, i); + Maat_table_runtime_plugin_new_row(table_rt, table_schema, row, logger); } - return key2ex_hash; -} -int Maat_table_runtime_plugin_new_ex_idx(struct Maat_table_runtime* table_rt, struct Maat_table_schema* table_desc, void* logger) -{ - assert(table_rt->plugin.key2ex_hash==NULL); - if(table_rt->plugin.key2ex_hash) - { - return -1; - } - table_rt->plugin.key2ex_hash=plugin_EX_htable_new(table_desc, table_rt->plugin.cache_lines, - table_rt->plugin.cache_line_num, logger); return 0; } -MAAT_PLUGIN_EX_DATA Maat_table_runtime_plugin_get_ex_data(struct Maat_table_runtime* table_rt, struct Maat_table_schema* table_desc, const char* key) +MAAT_PLUGIN_EX_DATA Maat_table_runtime_plugin_get_ex_data(struct Maat_table_runtime* table_rt, struct Maat_table_schema* table_schema, const char* key) { - struct plugin_table_schema* plugin_desc=NULL; - struct wrap_plugin_EX_data* wrap_data=NULL; - MAAT_RULE_EX_DATA exdata=NULL; - - plugin_desc=&(table_desc->plugin); - if(!plugin_desc->have_exdata) + MAAT_RULE_EX_DATA ex_data=NULL; + if(!table_schema->plugin.have_exdata) { assert(0); return NULL; } - wrap_data=(struct wrap_plugin_EX_data*)MESA_htable_search(table_rt->plugin.key2ex_hash, - (const unsigned char*)key, strlen(key)); - if(wrap_data!=NULL) - { - plugin_desc->ex_desc.dup_func(table_desc->table_id, &(exdata), &(wrap_data->exdata), - plugin_desc->ex_desc.argl, plugin_desc->ex_desc.argp); - } - return exdata; + ex_data=EX_data_rt_get_EX_data_by_key(table_rt->plugin.ex_data_rt, key, strlen(key)); + return ex_data; } -int Maat_table_runtime_ip_plugin_get_N_ex_data(struct Maat_table_runtime* table_rt, struct Maat_table_schema* table_schema, const struct ip_address* ip, MAAT_PLUGIN_EX_DATA* ex_data_array, size_t size) +void Maat_table_runtime_plugin_new_row(struct Maat_table_runtime* table_rt, struct Maat_table_schema* table_schema, const char* row, void *logger) { - struct ip_plugin_table_schema* ip_plugin_desc=NULL; - struct scan_result results[size]; - int n_result=0, i=0; + int ret=0, i=0; + size_t is_valid_offset=0, valid_len=0; + size_t key_offset=0, key_len=0; - ip_plugin_desc=&(table_schema->ip_plugin); - n_result=ip_matcher_match(table_rt->ip_plugin.ip_matcher, (struct ip_data*)ip, results, size); - for(i=0; iplugin; + struct plugin_runtime* plugin_rt=&table_rt->plugin; + ret=Maat_helper_read_column(row, plugin_schema->valid_flag_column, &is_valid_offset, &valid_len); + plugin_rt->acc_line_num++; + if(plugin_schema->have_exdata) { - - ip_plugin_desc->ex_desc.dup_func(table_schema->table_id, &(ex_data_array[i]), &(results[i].tag), - ip_plugin_desc->ex_desc.argl, ip_plugin_desc->ex_desc.argp); + ret=get_column_pos(row, plugin_schema->key_column, &key_offset, &key_len); + if(ret<0) + { + MESA_handle_runtime_log(logger, RLOG_LV_FATAL, maat_module, + "plugin EX data process error: cannot find column %d of %s", + plugin_schema->key_column, row); + return; + } + if(atoi(row+is_valid_offset)==1) + { + EX_data_rt_row2EX_data(plugin_rt->ex_data_rt, row, row+key_offset, key_len, NULL, logger); + } + else + { + EX_data_rt_delete_by_row(plugin_rt->ex_data_rt, row, row+key_offset, key_len, logger); + } } - return n_result; + if(plugin_schema->cb_plug_cnt>0) + { + for(i=0; icb_plug_cnt;i++) + { + plugin_schema->cb_plug[i].update(table_schema->table_id, row, plugin_schema->cb_plug[i].u_para); + } + } + if(!plugin_schema->have_exdata && !plugin_schema->cb_plug_cnt) + { + EX_data_rt_cache_row(plugin_rt->ex_data_rt, row); + } + + return; } void Maat_table_runtime_digest_add(struct Maat_table_runtime* table_rt, int expr_id, const char* digest, short confidence_degree, void* tag) @@ -518,34 +421,29 @@ int Maat_table_runtime_digest_batch_udpate(struct Maat_table_runtime* table_rt) return q_cnt; } -TAILQ_HEAD(ip_range_rule_q, wrap_plugin_EX_data); - -void walk_ip_plugin_hash(const uchar * key, uint size, void * data, void * user) -{ - struct wrap_plugin_EX_data* wrap_plugin_ex=(struct wrap_plugin_EX_data*)data; - struct ip_range_rule_q* queue=(struct ip_range_rule_q*)user; - TAILQ_INSERT_TAIL(queue, wrap_plugin_ex, entries); - return; -} - -int Maat_table_runtime_rebuild_ip_matcher(struct Maat_table_runtime* table_rt) +int Maat_table_runtime_ip_plugin_rebuild_ip_matcher(struct Maat_table_runtime* table_rt) { struct ip_matcher* new_ip_matcher=NULL; - struct ip_range_rule_q queue;//This is for index, no need to free. + struct EX_data_container_q queue;//This is for index, no need to free. size_t rule_cnt=0; size_t i=0, mem_use=0; struct ip_rule *rules=NULL; - struct wrap_plugin_EX_data *p=NULL; + struct EX_data_container *p=NULL; TAILQ_INIT(&queue); - MESA_htable_iterate(table_rt->ip_plugin.rowid2ex_hash, walk_ip_plugin_hash, &queue); - rule_cnt=(size_t)MESA_htable_get_elem_num(table_rt->ip_plugin.rowid2ex_hash); + rule_cnt=EX_data_rt_list_all(table_rt->ip_plugin.ex_data_rt, &queue); rules=ALLOC(struct ip_rule, rule_cnt); TAILQ_FOREACH(p, &queue, entries) { - rules[i]=p->range_rule; + rules[i]=*((struct ip_rule *)(p->user_data)); + assert(rules[i].user_tag==p||rules[i].user_tag==NULL); + rules[i].user_tag=p; i++; } assert(i==rule_cnt); + if(rule_cnt==0) + { + return 0; + } new_ip_matcher=ip_matcher_new(rules, rule_cnt, &mem_use); table_rt->ip_plugin.old_ip_matcher=table_rt->ip_plugin.ip_matcher; table_rt->ip_plugin.ip_matcher=new_ip_matcher; @@ -558,5 +456,82 @@ struct ip_matcher* Maat_table_runtime_dettach_old_ip_matcher(struct Maat_table_r table_rt->ip_plugin.old_ip_matcher=NULL; return old_one; } +void Maat_table_runtime_ip_plugin_new_row(struct Maat_table_runtime* table_rt, struct Maat_table_schema* table_schema, const char* row, void *logger) +{ + struct ip_plugin_table_schema* ip_plugin_schema=&(table_schema->ip_plugin); + struct ip_plugin_runtime* ip_plugin_rt=&(table_rt->ip_plugin); + size_t is_valid_offset=0, valid_len=0; + size_t key_offset=0, key_len=0; + struct ip_rule* ip_rule=NULL; + int ret=0; + + if(ip_plugin_schema->have_exdata) + { + ret=Maat_helper_read_column(row, ip_plugin_schema->valid_flag_column, &is_valid_offset, &valid_len); + if(ret<0) + { + MESA_handle_runtime_log(logger, RLOG_LV_FATAL, maat_module, + "ip_plugin EX data process error: cannot find is_valid column %d of %s", + ip_plugin_schema->row_id_column, row); + return; + } + ret=Maat_helper_read_column(row, ip_plugin_schema->row_id_column, &key_offset, &key_len); + if(ret<0) + { + MESA_handle_runtime_log(logger, RLOG_LV_FATAL, maat_module, + "ip_plugin EX data process error: cannot find row id column %d of %s", + ip_plugin_schema->row_id_column, row); + return; + } + ip_rule=ip_plugin_row2ip_rule(ip_plugin_schema, row); + if(ip_rule==NULL) + { + MESA_handle_runtime_log(logger, RLOG_LV_FATAL, maat_module, + "ip_plugin read ip error: %s", row); + return; + + } + if(atoi(row+is_valid_offset)==1)//add + { + EX_data_rt_row2EX_data(ip_plugin_rt->ex_data_rt, row, row+key_offset, key_len, ip_rule, logger); + } + else + { + EX_data_rt_delete_by_row(ip_plugin_rt->ex_data_rt, row, row+key_offset, key_len, logger); + } + } + else + { + EX_data_rt_cache_row(ip_plugin_rt->ex_data_rt, row); + } + return; +} +int Maat_table_runtime_ip_plugin_commit_ex_schema(struct Maat_table_runtime* table_rt, struct Maat_table_schema* table_schema, void* logger) +{ + int i=0; + const char* row=NULL; + EX_data_rt_set_schema(table_rt->ip_plugin.ex_data_rt, &table_schema->ip_plugin.ex_schema); + for(i=0; iplugin.ex_data_rt); i++) + { + row=EX_data_rt_get_cached_row(table_rt->plugin.ex_data_rt, i); + Maat_table_runtime_ip_plugin_new_row(table_rt, table_schema, row, logger); + } + EX_data_rt_clear_row_cache(table_rt->plugin.ex_data_rt); + Maat_table_runtime_ip_plugin_rebuild_ip_matcher(table_rt); + + return 0; +} + +int Maat_table_runtime_ip_plugin_get_N_ex_data(struct Maat_table_runtime* table_rt, struct Maat_table_schema* table_schema, const struct ip_data* ip, MAAT_PLUGIN_EX_DATA* ex_data_array, size_t size) +{ + struct scan_result results[size]; + int n_result=0, i=0; + n_result=ip_matcher_match(table_rt->ip_plugin.ip_matcher, (struct ip_data*)ip, results, size); + for(i=0; iip_plugin.ex_data_rt, (struct EX_data_container *)results[i].tag); + } + return n_result; +} diff --git a/src/inc_internal/Maat_ex_data.h b/src/inc_internal/Maat_ex_data.h new file mode 100644 index 0000000..47d7e7f --- /dev/null +++ b/src/inc_internal/Maat_ex_data.h @@ -0,0 +1,45 @@ + +#include "dynamic_array.h" +#include "Maat_rule.h" +#include + +#include + +struct EX_data_rt +{ + dynamic_array_t *cache_rows; + long long cache_row_num; + long long cache_size; + MESA_htable_handle key2ex_hash; + const struct EX_data_schema* ex_schema; + int table_id; + void (* user_data_free)(void *user_data); +}; +struct EX_data_container +{ + MAAT_RULE_EX_DATA ex_data; + const struct EX_data_rt* rt; + void* user_data; + TAILQ_ENTRY(EX_data_container) entries; +}; + +TAILQ_HEAD(EX_data_container_q, EX_data_container); +struct EX_data_rt* EX_data_rt_new(int table_id, long long estimate_size, Maat_plugin_EX_key2index_func_t * key2index, void (* user_data_free)(void *user_data)); +void EX_data_rt_free(struct EX_data_rt* p); +void EX_data_rt_set_schema(struct EX_data_rt* p, const struct EX_data_schema* schema); +void EX_data_rt_cache_row(struct EX_data_rt* p, const char* row); + +const char* EX_data_rt_get_cached_row(struct EX_data_rt* p, int i); + +void EX_data_rt_clear_row_cache(struct EX_data_rt* p); +int EX_data_rt_get_row_num(struct EX_data_rt* p); + +struct EX_data_container* EX_data_rt_row2EX_data(struct EX_data_rt* ex_rt, + const char* row, const char* key, size_t key_len, + void* user_data, void* logger); + +int EX_data_rt_delete_by_row(struct EX_data_rt* ex_rt, const char* row, const char* key, size_t key_len, void *logger); +MAAT_RULE_EX_DATA EX_data_rt_get_EX_data_by_key(struct EX_data_rt* ex_rt, const char* key, size_t key_len); +MAAT_RULE_EX_DATA EX_data_rt_get_EX_data_by_container(struct EX_data_rt* ex_rt, struct EX_data_container* container); +size_t EX_data_rt_list_all(struct EX_data_rt* ex_rt, EX_data_container_q* listed); + diff --git a/src/inc_internal/Maat_rule_internal.h b/src/inc_internal/Maat_rule_internal.h index 8746343..4a0d04c 100644 --- a/src/inc_internal/Maat_rule_internal.h +++ b/src/inc_internal/Maat_rule_internal.h @@ -457,9 +457,6 @@ void rewrite_table_line_with_foreign(struct serial_rule_t*p); void fill_maat_rule(struct Maat_rule_t *rule, const struct Maat_rule_head* rule_head, const char* srv_def, int srv_def_len); MAAT_RULE_EX_DATA rule_ex_data_new(const struct Maat_rule_head * rule_head, const char* srv_def, const struct compile_ex_data_idx* ex_desc); void rule_ex_data_free(const struct Maat_rule_head * rule_head, const char* srv_def, MAAT_RULE_EX_DATA *ad, const struct compile_ex_data_idx* ex_desc); -MESA_htable_handle wrap_plugin_EX_hash_new(long long estimate_size, Maat_plugin_EX_key2index_func_t * key2index); -int plugin_EX_data_new(const struct Maat_table_schema* plugin_table, const char* line, MESA_htable_handle key2ex_hash, void *logger); -int plugin_EX_data_free(const char* line, int key_column, MESA_htable_handle key2ex_hash, void *logger); void set_serial_rule(struct serial_rule_t* rule,enum MAAT_OPERATION op,int rule_id,int label_id,const char* table_name,const char* line, long long timeout); diff --git a/src/inc_internal/Maat_table.h b/src/inc_internal/Maat_table.h index 8e0b75c..3249b7b 100644 --- a/src/inc_internal/Maat_table.h +++ b/src/inc_internal/Maat_table.h @@ -79,7 +79,7 @@ struct plugin_table_callback_schema Maat_finish_callback_t *finish; void* u_para; }; -struct plugin_table_ex_data_schema +struct EX_data_schema { Maat_plugin_EX_new_func_t* new_func; Maat_plugin_EX_free_func_t* free_func; @@ -99,7 +99,7 @@ struct plugin_table_schema int have_exdata; long long estimate_size; struct plugin_table_callback_schema cb_plug[MAX_PLUGIN_PER_TABLE]; - struct plugin_table_ex_data_schema ex_desc; + struct EX_data_schema ex_schema; }; struct ip_plugin_table_schema { @@ -110,7 +110,8 @@ struct ip_plugin_table_schema int valid_flag_column; int rule_tag_column; long long estimate_size; - struct plugin_table_ex_data_schema ex_desc; + int have_exdata; + struct EX_data_schema ex_schema; }; struct Maat_table_schema { @@ -161,12 +162,21 @@ int Maat_table_new_compile_rule_ex_index(struct Maat_table_manager* table_mgr, c Maat_rule_EX_dup_func_t* dup_func, long argl, void *argp); struct compile_ex_data_idx* Maat_table_get_compile_rule_ex_desc(struct Maat_table_manager* table_mgr, const char* compile_table_name, int idx); -int Maat_table_plugin_new_ex_index(struct Maat_table_manager* table_mgr, int table_id, +int Maat_table_plugin_EX_data_schema_set(struct Maat_table_schema *table_schema, Maat_plugin_EX_new_func_t* new_func, Maat_plugin_EX_free_func_t* free_func, Maat_plugin_EX_dup_func_t* dup_func, Maat_plugin_EX_key2index_func_t* key2index_func, - long argl, void *argp); + long argl, void *argp, + void* logger); +int Maat_table_ip_plugin_EX_data_schema_set(struct Maat_table_schema *table_schema, + Maat_plugin_EX_new_func_t* new_func, + Maat_plugin_EX_free_func_t* free_func, + Maat_plugin_EX_dup_func_t* dup_func, + Maat_plugin_EX_key2index_func_t* key2index_func, + long argl, void *argp, + void* logger); + void Maat_table_manager_all_plugin_cb_start(struct Maat_table_manager* table_mgr, int update_type); void Maat_table_manager_all_plugin_cb_finish(struct Maat_table_manager* table_mgr); diff --git a/src/inc_internal/Maat_table_runtime.h b/src/inc_internal/Maat_table_runtime.h index 57c6e2e..936e7c1 100644 --- a/src/inc_internal/Maat_table_runtime.h +++ b/src/inc_internal/Maat_table_runtime.h @@ -1,4 +1,5 @@ #include "Maat_table.h" +#include "Maat_ex_data.h" #include "IPMatcher.h" #include "gram_index_engine.h" #include "alignment_int64.h" @@ -14,16 +15,13 @@ struct similar_runtime struct plugin_runtime { - dynamic_array_t *cache_lines; - long long cache_line_num; + struct EX_data_rt* ex_data_rt; long long acc_line_num; - long long cache_size; - MESA_htable_handle key2ex_hash; }; + struct ip_plugin_runtime { - long long row_num; - MESA_htable_handle rowid2ex_hash; + struct EX_data_rt* ex_data_rt; struct ip_matcher* ip_matcher; struct ip_matcher* old_ip_matcher; }; @@ -69,13 +67,18 @@ struct Maat_table_runtime* Maat_table_runtime_get(struct Maat_table_runtime_mana long long Maat_table_runtime_plugin_cached_line_count(struct Maat_table_runtime* table_rt); const char* Maat_table_runtime_plugin_get_cached_line(struct Maat_table_runtime* table_rt, long long Nth_line); -int Maat_table_runtime_plugin_new_ex_idx(struct Maat_table_runtime* table_rt, struct Maat_table_schema* table_desc, void* logger); +int Maat_table_runtime_plugin_commit_ex_schema(struct Maat_table_runtime* table_rt, struct Maat_table_schema* table_desc, void* logger); MAAT_PLUGIN_EX_DATA Maat_table_runtime_plugin_get_ex_data(struct Maat_table_runtime* table_rt, struct Maat_table_schema* table_desc, const char* key); void Maat_table_runtime_digest_add(struct Maat_table_runtime* table_rt, int expr_id, const char* digest, short confidence_degree, void* tag); void Maat_table_runtime_digest_del(struct Maat_table_runtime* table_rt, int expr_id); int Maat_table_runtime_digest_batch_udpate(struct Maat_table_runtime* table_rt); -int Maat_table_runtime_ip_plugin_get_N_ex_data(struct Maat_table_runtime* table_rt, struct Maat_table_schema* table_schema, const struct ip_address* ip, MAAT_PLUGIN_EX_DATA* ex_data_array, size_t size); -int Maat_table_runtime_rebuild_ip_matcher(struct Maat_table_runtime* table_rt); + +void Maat_table_runtime_plugin_new_row(struct Maat_table_runtime* table_rt, struct Maat_table_schema* table_schema, const char* row, void *logger); +void Maat_table_runtime_ip_plugin_new_row(struct Maat_table_runtime* table_rt, struct Maat_table_schema* table_schema, const char* row, void *logger); + +int Maat_table_runtime_ip_plugin_commit_ex_schema(struct Maat_table_runtime* table_rt, struct Maat_table_schema* table_schema, void* logger); +int Maat_table_runtime_ip_plugin_get_N_ex_data(struct Maat_table_runtime* table_rt, struct Maat_table_schema* table_schema, const struct ip_data* ip, MAAT_PLUGIN_EX_DATA* ex_data_array, size_t size); +int Maat_table_runtime_ip_plugin_rebuild_ip_matcher(struct Maat_table_runtime* table_rt); struct ip_matcher* Maat_table_runtime_dettach_old_ip_matcher(struct Maat_table_runtime* table_rt); diff --git a/test/maat_json.json b/test/maat_json.json index 00e00bb..5692650 100644 --- a/test/maat_json.json +++ b/test/maat_json.json @@ -1754,6 +1754,15 @@ "1000000130\t1000000130\t4\t192.168.10.99\t255.255.255.255\t0\t65535\t0.0.0.0\t255.255.255.255\t0\t65535\t0\t1\t1\t96\t1\tuser_region\t{}\t2019/1/24/18:0:34", "161\t161\t4\t0.0.0.0\t255.255.255.255\t0\t65535\t61.135.169.121\t255.255.255.255\t0\t65535\t0\t0\t1\t96\t832\t0\t0\t2019/1/24/18:48:42" ] + }, + { + "table_name": "TEST_IP_PLUGIN_WITH_EXDATA", + "table_content": [ + "101\t4\t192.168.30.99\t192.168.30.101\tSomething-like-json\t1", + "102\t4\t192.168.30.90\t192.168.30.128\tBigger-range-should-in-the-back\t1", + "103\t6\t2001:db8:1234::\t2001:db8:1235::\tBigger-range-should-in-the-back\t1", + "104\t6\t2001:db8:1234::1\t2001:db8:1234::5210\tSomething-like-json\t1" + ] } ] } diff --git a/test/table_info.conf b/test/table_info.conf index 82e6924..f577ffb 100644 --- a/test/table_info.conf +++ b/test/table_info.conf @@ -47,4 +47,5 @@ 24 COMPOSITION_IP_SOURCE virtual IP_PLUS_CONFIG -- 25 COMPOSITION_IP_DESTINATION virtual IP_PLUS_CONFIG -- 26 COMPOSITION_IP_SESSION virtual IP_PLUS_CONFIG -- -27 COMPOSITION_IP composition {"source":"COMPOSITION_IP_SOURCE","destination":"COMPOSITION_IP_DESTINATION","session":"COMPOSITION_IP_SESSION"} \ No newline at end of file +27 COMPOSITION_IP composition {"source":"COMPOSITION_IP_SOURCE","destination":"COMPOSITION_IP_DESTINATION","session":"COMPOSITION_IP_SESSION"} +28 TEST_IP_PLUGIN_WITH_EXDATA ip_plugin {"row_id":1,"ip_type":2,"start_ip":3,"end_ip":4,"valid":6} -- \ No newline at end of file diff --git a/test/test_maatframe.cpp b/test/test_maatframe.cpp index 103f4fb..d17c940 100644 --- a/test/test_maatframe.cpp +++ b/test/test_maatframe.cpp @@ -101,7 +101,7 @@ void scan_with_old_or_new_cfg(Maat_feather_t feather, int is_old) if(!is_old) { EXPECT_EQ(ret, 1); - EXPECT_TRUE(result.config_id==2); + EXPECT_EQ(result.config_id, 2); } else { @@ -219,7 +219,7 @@ void test_plugin_table(Maat_feather_t feather,const char* table_name, ASSERT_GT(ret, 0); } - +#define Plugin_callback TEST(PluginTable, Callback) { test_plugin_table(g_feather, "QD_ENTRY_INFO", @@ -229,6 +229,85 @@ TEST(PluginTable, Callback) g_feather, g_logger); +} +#define IP_PLUGIN_EX_DATA +struct ip_plugin_ud +{ + int rule_id; + char* buffer; + int ref_cnt; +}; +void ip_plugin_EX_new_cb(int table_id, const char* key, const char* table_line, MAAT_PLUGIN_EX_DATA* ad, long argl, void *argp) +{ + int *counter=(int *)argp, ret=0; + size_t column_offset=0, column_len=0; + struct ip_plugin_ud* ud=(struct ip_plugin_ud*)calloc(sizeof(struct ip_plugin_ud), 1); + ret=Maat_helper_read_column(table_line, 1, &column_offset, &column_len); + EXPECT_EQ(ret, 0); + ud->rule_id=atoi(table_line+column_offset); + ret=Maat_helper_read_column(table_line, 5, &column_offset, &column_len); + EXPECT_EQ(ret, 0); + ud->buffer=(char*)calloc(sizeof(char), column_len+1); + strncpy(ud->buffer, table_line+column_offset, column_len); + ud->ref_cnt=1; + *ad=ud; + (*counter)++; + return; +} +void ip_plugin_EX_free_cb(int table_id, MAAT_PLUGIN_EX_DATA* ad, long argl, void *argp) +{ + struct ip_plugin_ud* u=(struct ip_plugin_ud*)(*ad); + u->ref_cnt--; + if(u->ref_cnt>0) return; + free(u->buffer); + free(u); + *ad=NULL; +} +void ip_plugin_EX_dup_cb(int table_id, MAAT_PLUGIN_EX_DATA *to, MAAT_PLUGIN_EX_DATA *from, long argl, void *argp) +{ + struct ip_plugin_ud* u=(struct ip_plugin_ud*)(*from); + u->ref_cnt++; + *to=u; +} + +TEST(IP_Plugin_Table, EX_DATA) +{ + + int ip_plugin_ex_data_counter=0, i=0; + const char* table_name="TEST_IP_PLUGIN_WITH_EXDATA"; + int table_id=0, ret=0; + table_id=Maat_table_register(g_feather, table_name); + ASSERT_GT(table_id, 0); + ret=Maat_ip_plugin_EX_register(g_feather, table_id, + ip_plugin_EX_new_cb, + ip_plugin_EX_free_cb, + ip_plugin_EX_dup_cb, + 0, &ip_plugin_ex_data_counter); + ASSERT_TRUE(ret>=0); + EXPECT_EQ(ip_plugin_ex_data_counter, 4); + struct ip_address ipv4, ipv6; + struct ip_plugin_ud* result[4]; + ipv4.ip_type=4; + inet_pton(AF_INET, "192.168.30.100", &(ipv4.ipv4)); + ret=Maat_ip_plugin_get_EX_data(g_feather, table_id, &ipv4, (void**)result, 4); + ASSERT_EQ(ret, 2); + EXPECT_EQ(result[0]->rule_id, 101); + EXPECT_EQ(result[1]->rule_id, 102); + for(i=0; irule_id, 104); + EXPECT_EQ(result[1]->rule_id, 103); + for(i=0; i Date: Wed, 13 May 2020 14:49:03 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E6=9B=B4=E6=96=B0ip=5Fmatcher=EF=BC=8C?= =?UTF-8?q?=E8=A7=A3=E5=86=B3IPv6=E5=AD=97=E8=8A=82=E5=BA=8F=E9=97=AE?= =?UTF-8?q?=E9=A2=98=E3=80=82=20=E6=A0=B9=E6=8D=AErfc2553=EF=BC=8Cipv6?= =?UTF-8?q?=E5=9C=B0=E5=9D=80=E6=98=AF=E4=B8=80=E4=B8=AA16=E5=AD=97?= =?UTF-8?q?=E8=8A=82=E7=9A=84=E6=95=B0=E7=BB=84=EF=BC=8C=E7=94=A8=E7=BD=91?= =?UTF-8?q?=E7=BB=9C=E5=BA=8F=E5=AD=98=E5=82=A8=E3=80=82=203.2=20IPv6=20Ad?= =?UTF-8?q?dress=20Structure=20=20=20=20A=20new=20in6=5Faddr=20structure?= =?UTF-8?q?=20holds=20a=20single=20IPv6=20address=20and=20is=20defined=20?= =?UTF-8?q?=20=20=20as=20a=20result=20of=20including=20:=20?= =?UTF-8?q?=20=20=20=20=20=20struct=20in6=5Faddr=20{=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20uint8=5Ft=20=20s6=5Faddr[16];=20=20=20=20=20=20/*?= =?UTF-8?q?=20IPv6=20address=20*/=20=20=20=20=20=20=20};=20This=20data=20s?= =?UTF-8?q?tructure=20contains=20an=20array=20of=20sixteen=208-bit=20eleme?= =?UTF-8?q?nts,=20which=20make=20up=20one=20128-bit=20IPv6=20address.=20Th?= =?UTF-8?q?e=20IPv6=20address=20is=20stored=20in=20network=20byte=20order.?= =?UTF-8?q?=20IPv6=E5=9C=B0=E5=9D=80=E7=94=B1=E9=AB=98=E4=BD=8D=E5=88=B0?= =?UTF-8?q?=E4=BD=8E=E4=BD=8D=E5=AD=98=E5=82=A8=E5=9C=A8=E7=AC=AC0?= =?UTF-8?q?=E8=87=B315=E4=B8=AAuint8=5Ft=E4=B8=AD=E3=80=82=E4=BE=8B?= =?UTF-8?q?=E5=A6=82=E5=9C=B0=E5=9D=802001:0db8:1234::5210=EF=BC=8Crfc2553?= =?UTF-8?q?=E5=AD=98=E5=82=A8=E4=B8=BA=EF=BC=88=E9=80=9A=E8=BF=87inet=5Fpt?= =?UTF-8?q?on=EF=BC=89=EF=BC=9A=20static=20const=20uint8=5Ft=20myaddr[16]?= =?UTF-8?q?=20=3D=20{=200x20,=200x01,=200x0d,=200xb8,=200x12,=200x34,=200x?= =?UTF-8?q?00,=200x00,=200x00,=200x00,=200x00,=200x00,=200x00,=200x00,=200?= =?UTF-8?q?x52,=200x10=20};?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 涓婅堪鍦板潃杞崲涓簎int32_t缃戠粶搴忓悗锛 static const uint32_t myaddr_network_byte_order[4]={ 0xb80d0120, 0x3412, 0x0, 0x10520000} 鍐嶈浆鎹负涓绘満搴忓悗锛岃繖涔熸槸MAAT杈撳叆ipmatcher鍜宺ulescan鐨勬牸寮忥細 static const uint32_t myaddr_host_byte_order[4]={ 0x20010db8, 0x12340000, 0x0, 0x5210} 鐩墠锛宨p_matcher涓璱nt鏁扮粍楂樹綅鍒颁綆浣嶇殑椤哄簭鏄痑[3]a[2]a[1]a[0]锛屼笌RFC2553銆丩inux閮戒笉涓鑷淬 --- vendor/CMakeLists.txt | 2 +- ...er-20200503.zip => IPMatcher-20200513.zip} | Bin 86786 -> 88689 bytes 2 files changed, 1 insertion(+), 1 deletion(-) rename vendor/{IPMatcher-20200503.zip => IPMatcher-20200513.zip} (68%) diff --git a/vendor/CMakeLists.txt b/vendor/CMakeLists.txt index fb50be3..19bd039 100644 --- a/vendor/CMakeLists.txt +++ b/vendor/CMakeLists.txt @@ -48,7 +48,7 @@ set_property(TARGET igraph-static PROPERTY IMPORTED_LOCATION ${INSTALL_DIR}/lib/ set_property(TARGET igraph-static PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${INSTALL_DIR}/include) ExternalProject_Add(ipmatcher PREFIX ipmatcher - URL ${CMAKE_CURRENT_SOURCE_DIR}/IPMatcher-20200503.zip + URL ${CMAKE_CURRENT_SOURCE_DIR}/IPMatcher-20200513.zip CONFIGURE_COMMAND "" BUILD_COMMAND make INSTALL_COMMAND make DESTDIR= install diff --git a/vendor/IPMatcher-20200503.zip b/vendor/IPMatcher-20200513.zip similarity index 68% rename from vendor/IPMatcher-20200503.zip rename to vendor/IPMatcher-20200513.zip index 921f29435de9a825ba39bee3a0bfe975ff3585bf..5b9ce89d086cd94411acfd9871463ddeee13d018 100644 GIT binary patch delta 19544 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