From 8a7c196c20dea2005085ded654f207936608c8ba Mon Sep 17 00:00:00 2001 From: wangmenglan Date: Mon, 24 Apr 2023 10:48:40 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=96=87=E4=BB=B6=E7=BC=96?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cache/src/tango_cache_client.cpp | 32 +- common/CMakeLists.txt | 1 + common/include/tfe_acceptor_kni.h | 27 +- common/include/tfe_ctrl_packet.h | 5 + common/include/tfe_metrics.h | 8 + common/src/tfe_acceptor_kni.cpp | 26 +- common/src/tfe_cmsg.cpp | 31 +- common/src/tfe_ctrl_packet.cpp | 9 - common/src/tfe_metrics.cpp | 47 +- common/src/tfe_mpack.cpp | 197 ++- common/src/tfe_packet_io.cpp | 2076 ++++++++++++++-------------- common/src/tfe_utils.cpp | 2 +- common/test/test_mpack.cpp | 16 + common/test/test_session_table.cpp | 274 ++++ platform/src/acceptor_kni_v4.cpp | 26 +- platform/src/proxy.cpp | 3 + vendor/CMakeLists.txt | 2 +- vendor/msgpack-c-6.0.0.tar.gz | Bin 68965 -> 69341 bytes 18 files changed, 1588 insertions(+), 1194 deletions(-) create mode 100644 common/test/test_mpack.cpp create mode 100644 common/test/test_session_table.cpp diff --git a/cache/src/tango_cache_client.cpp b/cache/src/tango_cache_client.cpp index 80a462d..cdfb1bc 100644 --- a/cache/src/tango_cache_client.cpp +++ b/cache/src/tango_cache_client.cpp @@ -47,7 +47,7 @@ void caculate_sha256(const char *data, unsigned long len, char *result, u_int32_ SHA256_Update(&c, data, len); SHA256_Final(sha256, &c); - length = (size > 64)?32:(size-1)/2; //Ԥ��һ���ռ� + length = (size > 64)?32:(size-1)/2; //Ԥһռ for(u_int32_t i=0; ibuff[estr->len]='\0'; } -//callback: �Ƿ���ûص���������ҪΪ���ֱ�ӵ���APIʱʧ�ܣ����ٵ��ûص�������ͨ������ֵ�ж� +//callback: ǷûصҪΪֱӵAPIʱʧܣٵûصֵͨж void tango_cache_ctx_destroy(struct tango_cache_ctx *ctx, bool callback) { struct multipart_etag_list *etag; @@ -255,7 +255,7 @@ void tango_cache_ctx_destroy(struct tango_cache_ctx *ctx, bool callback) free(ctx); } -//�ж�session�Ƿ񳬹����ƣ�����ȡ��ʼ���ж�where_to_get�Ƿ�ȫ����MINIO�� +//жsessionǷ񳬹ƣȡʼжwhere_to_getǷȫMINIO bool sessions_exceeds_limit(struct tango_cache_instance *instance, enum OBJECT_LOCATION where_to_get) { if(where_to_get == OBJECT_IN_HOS) @@ -269,7 +269,7 @@ bool sessions_exceeds_limit(struct tango_cache_instance *instance, enum OBJECT_L } -//�����ϴ�API��ʹ��ctx��evbuffer�������޷�����ctx��ȡ���� +//ϴAPIʹctxevbuffer޷ctxȡ enum OBJECT_LOCATION tango_cache_object_locate(struct tango_cache_instance *instance, size_t object_size) { if(instance->param->fsstatid_trig) @@ -340,7 +340,7 @@ int tango_cache_update_frag_data(struct tango_cache_ctx *ctx, const char *data, { if(ctx->fail_state) { - return 0; //TODO: ��ʱ���Է���ֵ!! + return 0; //TODO: ʱԷֵ!! } if(evbuffer_add(ctx->put.evbuf, data, size)) { @@ -362,7 +362,7 @@ int tango_cache_update_frag_evbuf(struct tango_cache_ctx *ctx, enum EVBUFFER_COP if(ctx->fail_state) { - return 0;//TODO: ��ʱ���Է���ֵ!! + return 0;//TODO: ʱԷֵ!! } size = evbuffer_get_length(evbuf); @@ -424,7 +424,7 @@ struct tango_cache_ctx *tango_cache_update_prepare(struct tango_cache_instance * { snprintf(ctx->object_key, 256, "%s/%c%c_%c%c_%s", instance->param->bucketname, buffer[0], buffer[1], buffer[2], buffer[3], buffer+4); } - //����ԭʼURL + //ԭʼURL snprintf(buffer, 2064, "x-amz-meta-url: %s", meta->url); ctx->headers = curl_slist_append(ctx->headers, buffer); } @@ -441,7 +441,7 @@ struct tango_cache_ctx *tango_cache_update_prepare(struct tango_cache_instance * return NULL; } - //Expires�ֶΣ����ڻ����ڲ��ж������Ƿ�ʱ + //ExpiresֶΣڻڲжǷʱ now = time(NULL); expires = (meta->put.timeout==0||meta->put.timeout>instance->param->relative_ttl)?instance->param->relative_ttl:meta->put.timeout; if(expires_timestamp2hdr_str(now + expires, buffer, 256)) @@ -449,7 +449,7 @@ struct tango_cache_ctx *tango_cache_update_prepare(struct tango_cache_instance * ctx->headers = curl_slist_append(ctx->headers, buffer); } ctx->put.object_ttl = expires; - //Last-Modify�ֶΣ�����GETʱ�ж��Ƿ����� + //Last-ModifyֶΣGETʱжǷ last_modify = (meta->put.date > meta->put.last_modified)?meta->put.date:meta->put.last_modified; if(last_modify == 0) { @@ -457,7 +457,7 @@ struct tango_cache_ctx *tango_cache_update_prepare(struct tango_cache_instance * } sprintf(buffer, "x-amz-meta-lm: %lu", last_modify); ctx->headers = curl_slist_append(ctx->headers, buffer); - //�б���֧�ֵı�׼ͷ�� + //бֵ֧ı׼ͷ for(int i=0; istd_hdr[i] != NULL) @@ -478,11 +478,11 @@ struct tango_cache_ctx *tango_cache_update_prepare(struct tango_cache_instance * easy_string_savedata(&hdr_estr, "Content-Type: application/octet-stream\r\n", strlen("Content-Type: application/octet-stream\r\n")); } } - ctx->headers = curl_slist_append(ctx->headers, "Expect:");//ע��POST������Expect��ϵ��Ҫ��ȷ����CURLOPT_POSTFIELDSIZE - //���������ͷ����GETʱ��ԭ������ + ctx->headers = curl_slist_append(ctx->headers, "Expect:");//עPOSTExpectϵҪȷCURLOPT_POSTFIELDSIZE + //ͷGETʱԭ if(meta->usertag_len>0 && meta->usertag_len<=USER_TAG_MAX_LEN) { - user_tag = (char *)malloc((meta->usertag_len/3 + 1)*4 + 18); //������������ռ䣻18=17+1: ͷ��+�ַ��������� + user_tag = (char *)malloc((meta->usertag_len/3 + 1)*4 + 18); //ռ䣻18=17+1: ͷ+ַ memcpy(user_tag, "x-amz-meta-user: ", 17); user_tag_value = user_tag+17; Base64_EncodeBlock((const unsigned char*)meta->usertag, meta->usertag_len, (unsigned char*)user_tag_value); @@ -535,7 +535,7 @@ struct tango_cache_ctx *tango_cache_update_start(struct tango_cache_instance *in return ctx; } -//һ�����ϴ�ʱ��ֱ�Ӷ�λ�����ϴ���λ�� +//һϴʱֱӶλϴλ struct tango_cache_ctx *tango_cache_update_once_prepare(struct tango_cache_instance *instance, struct future* f, struct tango_cache_meta_put *meta, size_t object_size, char *path, size_t pathsize) { @@ -600,7 +600,7 @@ struct tango_cache_ctx *tango_cache_fetch_prepare(struct tango_cache_instance *i ctx = (struct tango_cache_ctx *)calloc(1, sizeof(struct tango_cache_ctx)); ctx->instance = instance; ctx->promise = future_to_promise(f); - promise_allow_many_successes(ctx->promise); //��λص��������ʱ����promise_finish + promise_allow_many_successes(ctx->promise); //λصʱpromise_finish ctx->method = method; ctx->get.state = GET_STATE_START; ctx->get.max_age = meta->get.max_age; @@ -647,7 +647,7 @@ int tango_cache_head_object(struct tango_cache_instance *instance, struct future struct tango_cache_ctx *ctx; enum OBJECT_LOCATION location; - //���������Redis����Ԫ��Ϣ�洢��Redis�� + //RedisԪϢ洢Redis location = (instance->param->object_store_way != CACHE_ALL_HOS)?OBJECT_IN_REDIS:OBJECT_IN_HOS; ctx = tango_cache_fetch_prepare(instance, CACHE_REQUEST_HEAD, f, meta, location); if(ctx == NULL) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 8c98f04..cebf8a3 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -10,6 +10,7 @@ target_include_directories(common PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include) target_include_directories(common PRIVATE ${CMAKE_CURRENT_LIST_DIR}/../platform/include/internal) target_link_libraries(common PUBLIC libevent-static libevent-static-openssl libevent-static-pthreads rdkafka) target_link_libraries(common PUBLIC MESA_handle_logger cjson msgpack) +target_link_libraries(common PUBLIC pthread) if (SUPPORT_LIBURING) target_link_libraries(common PUBLIC uring) diff --git a/common/include/tfe_acceptor_kni.h b/common/include/tfe_acceptor_kni.h index 7bb3fb3..3871dbc 100644 --- a/common/include/tfe_acceptor_kni.h +++ b/common/include/tfe_acceptor_kni.h @@ -7,6 +7,7 @@ extern "C" #endif #include +#include // #include "proxy.h" #include "tfe_utils.h" @@ -84,35 +85,31 @@ extern "C" struct packet_info { - int dir_is_e2i; + int is_e2i_dir; struct addr_tuple4 tuple4; - char *addr_string; char *header_data; int header_len; - - struct sids sids; - struct route_ctx route_ctx; }; struct session_ctx { int policy_ids; uint64_t session_id; + char *session_addr; - uint16_t user_field; + char client_mac[6]; + char server_mac[6]; - struct route_ctx raw_pkt_i2e_route_ctx; - struct route_ctx raw_pkt_e2i_route_ctx; + struct packet_info c2s_info; + struct packet_info s2c_info; - struct sids raw_pkt_i2e_sids; - struct sids raw_pkt_e2i_sids; + struct metadata *raw_meta_i2e; + struct metadata *raw_meta_e2i; + struct metadata *ctrl_meta; - // depending on first control packet - struct packet_info first_ctrl_pkt; - - // 加锁 struct tfe_cmsg *cmsg; + struct acceptor_thread_ctx *ref_thread_ctx; }; @@ -133,10 +130,8 @@ extern "C" cpu_set_t coremask; struct tap_config *config; - struct timestamp *ts; struct packet_io *io; struct global_metrics *metrics; - struct policy_enforcer *enforcer; struct acceptor_thread_ctx work_threads[TFE_THREAD_MAX]; struct tfe_proxy *ref_proxy; diff --git a/common/include/tfe_ctrl_packet.h b/common/include/tfe_ctrl_packet.h index 2ae43db..fbdd277 100644 --- a/common/include/tfe_ctrl_packet.h +++ b/common/include/tfe_ctrl_packet.h @@ -27,6 +27,11 @@ struct ctrl_pkt_parser uint64_t sce_policy_ids[32]; int sce_policy_id_num; struct tfe_cmsg *cmsg; + + struct sids seq_sids; + struct route_ctx seq_route_ctx; + struct sids ack_sids; + struct route_ctx ack_route_ctx; }; const char *session_state_to_string(enum session_state state); diff --git a/common/include/tfe_metrics.h b/common/include/tfe_metrics.h index 32c76e7..d96277a 100644 --- a/common/include/tfe_metrics.h +++ b/common/include/tfe_metrics.h @@ -32,6 +32,14 @@ struct global_metrics struct throughput_metrics decrypt_rx; // 累计值 struct throughput_metrics ctrl_pkt_rx; // 累计值 + struct throughput_metrics ctrl_pkt_tx; // 累计值 + + struct throughput_metrics tap_pkt_rx; // 累计值 + struct throughput_metrics tap_pkt_tx; // 累计值 + struct throughput_metrics tap_c_pkt_rx; // 累计值 + struct throughput_metrics tap_c_pkt_tx; // 累计值 + struct throughput_metrics tap_s_pkt_rx; // 累计值 + struct throughput_metrics tap_s_pkt_tx; // 累计值 uint64_t ctrl_pkt_opening_num; // 累计值 uint64_t ctrl_pkt_active_num; // 累计值 diff --git a/common/src/tfe_acceptor_kni.cpp b/common/src/tfe_acceptor_kni.cpp index 13e8846..a3a54be 100644 --- a/common/src/tfe_acceptor_kni.cpp +++ b/common/src/tfe_acceptor_kni.cpp @@ -21,18 +21,6 @@ void session_ctx_free(struct session_ctx *ctx) { if (ctx) { - if (ctx->first_ctrl_pkt.addr_string) - { - free(ctx->first_ctrl_pkt.addr_string); - ctx->first_ctrl_pkt.addr_string = NULL; - } - - if (ctx->first_ctrl_pkt.header_data) - { - free(ctx->first_ctrl_pkt.header_data); - ctx->first_ctrl_pkt.header_data = NULL; - } - if (ctx->cmsg) { tfe_cmsg_destroy(ctx->cmsg); @@ -43,13 +31,11 @@ void session_ctx_free(struct session_ctx *ctx) } } - /****************************************************************************** * acceptor_ctx ******************************************************************************/ struct acceptor_ctx *acceptor_ctx_create(const char *profile) { - int ret = 0; struct acceptor_ctx *ctx = ALLOC(struct acceptor_ctx, 1); MESA_load_profile_int_def(profile, "system", "firewall_sids", (int *)&(ctx->firewall_sids), 1001); @@ -83,17 +69,6 @@ struct acceptor_ctx *acceptor_ctx_create(const char *profile) goto error_out; } - // ctx->enforcer = policy_enforcer_create("KNI", profile, ctx->nr_worker_threads, NULL); - // if (ctx->enforcer == NULL) - // { - // goto error_out; - // } - - // if (policy_enforcer_register(ctx->enforcer) == -1) - // { - // goto error_out; - // } - return ctx; error_out: @@ -107,6 +82,7 @@ void acceptor_ctx_destory(struct acceptor_ctx * ctx) { packet_io_destory(ctx->io); tfe_tap_destory(ctx->config); + global_metrics_destory(ctx->metrics); free(ctx); ctx = NULL; diff --git a/common/src/tfe_cmsg.cpp b/common/src/tfe_cmsg.cpp index 1d934f2..8b01030 100644 --- a/common/src/tfe_cmsg.cpp +++ b/common/src/tfe_cmsg.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "tfe_types.h" #include "tfe_utils.h" @@ -23,6 +24,7 @@ struct tfe_cmsg_tlv struct tfe_cmsg { + pthread_rwlock_t rwlock; uint16_t nr_tlvs; struct tfe_cmsg_tlv* tlvs[TFE_CMSG_TLV_NR_MAX]; uint16_t size; @@ -39,6 +41,8 @@ struct tfe_cmsg* tfe_cmsg_init() { struct tfe_cmsg *cmsg = ALLOC(struct tfe_cmsg, 1); cmsg->size = sizeof(struct tfe_cmsg_serialize_header); + + pthread_rwlock_init(&(cmsg->rwlock), NULL); return cmsg; } @@ -46,11 +50,15 @@ void tfe_cmsg_destroy(struct tfe_cmsg *cmsg) { if(cmsg != NULL) { + pthread_rwlock_wrlock(&cmsg->rwlock); for(int i = 0; i < TFE_CMSG_TLV_NR_MAX; i++) { FREE(&(cmsg->tlvs[i])); } + pthread_rwlock_unlock(&cmsg->rwlock); + pthread_rwlock_destroy(&cmsg->rwlock); } + FREE(&cmsg); } @@ -60,6 +68,7 @@ int tfe_cmsg_set(struct tfe_cmsg * cmsg, enum tfe_cmsg_tlv_type type, const unsi { return TFE_CMSG_INVALID_TYPE; } + pthread_rwlock_wrlock(&cmsg->rwlock); struct tfe_cmsg_tlv *tlv = cmsg->tlvs[type]; uint16_t length = sizeof(struct tfe_cmsg_tlv) + size; @@ -83,6 +92,7 @@ int tfe_cmsg_set(struct tfe_cmsg * cmsg, enum tfe_cmsg_tlv_type type, const unsi tlv->length = length; memcpy(tlv->value_as_string, value, size); cmsg->tlvs[type] = tlv; + pthread_rwlock_unlock(&cmsg->rwlock); return 0; } @@ -99,6 +109,7 @@ int tfe_cmsg_get_value(struct tfe_cmsg * cmsg, enum tfe_cmsg_tlv_type type, unsi goto errout; } + pthread_rwlock_rdlock(&cmsg->rwlock); tlv = cmsg->tlvs[type]; if (unlikely(tlv == NULL)) { @@ -115,31 +126,38 @@ int tfe_cmsg_get_value(struct tfe_cmsg * cmsg, enum tfe_cmsg_tlv_type type, unsi memcpy(out_value, tlv->value_as_string, value_length); *out_size = value_length; - + pthread_rwlock_unlock(&cmsg->rwlock); return 0; errout: + pthread_rwlock_unlock(&cmsg->rwlock); return result; } uint16_t tfe_cmsg_serialize_size_get(struct tfe_cmsg *cmsg) { - return cmsg->size; + pthread_rwlock_rdlock(&cmsg->rwlock); + uint16_t size = cmsg->size; + pthread_rwlock_unlock(&cmsg->rwlock); + return size; } int tfe_cmsg_serialize(struct tfe_cmsg *cmsg, unsigned char *buff, uint16_t bufflen, uint16_t *serialize_len) { //size是serialize之后的实际长度 + pthread_rwlock_rdlock(&cmsg->rwlock); uint16_t size = cmsg->size; //传入buff是否够长 if(bufflen < size) { + pthread_rwlock_unlock(&cmsg->rwlock); return TFE_CMSG_BUFF_NOT_ENOUGH; } //size是否正确 if(size < sizeof(struct tfe_cmsg_serialize_header)) { - return TFE_CMSG_INVALID_FORMAT; + pthread_rwlock_unlock(&cmsg->rwlock); + return TFE_CMSG_BUFF_NOT_ENOUGH; } struct tfe_cmsg_serialize_header *header = (struct tfe_cmsg_serialize_header*)buff; header->__magic__[0] = 0x4d; @@ -156,6 +174,7 @@ int tfe_cmsg_serialize(struct tfe_cmsg *cmsg, unsigned char *buff, uint16_t buff } if(count != cmsg->nr_tlvs) { + pthread_rwlock_unlock(&cmsg->rwlock); return TFE_CMSG_INVALID_FORMAT; } //序列化 @@ -168,11 +187,13 @@ int tfe_cmsg_serialize(struct tfe_cmsg *cmsg, unsigned char *buff, uint16_t buff } if(i != tlv->type) { + pthread_rwlock_unlock(&cmsg->rwlock); return TFE_CMSG_INVALID_FORMAT; } uint16_t length = tlv->length; if(length < sizeof(struct tfe_cmsg_tlv) || offset + length > size) { + pthread_rwlock_unlock(&cmsg->rwlock); return TFE_CMSG_INVALID_FORMAT; } memcpy((char*)header + offset, (void*)tlv, length); @@ -184,9 +205,11 @@ int tfe_cmsg_serialize(struct tfe_cmsg *cmsg, unsigned char *buff, uint16_t buff //检查size是否正确 if(offset != size) { + pthread_rwlock_unlock(&cmsg->rwlock); return TFE_CMSG_INVALID_FORMAT; } *serialize_len = size; + pthread_rwlock_unlock(&cmsg->rwlock); return 0; } @@ -235,7 +258,9 @@ int tfe_cmsg_deserialize(const unsigned char *data, uint16_t len, struct tfe_cms offset += length; } cmsg->size = offset; + pthread_rwlock_wrlock(&((*pcmsg)->rwlock)); *pcmsg = cmsg; + pthread_rwlock_unlock(&((*pcmsg)->rwlock)); return 0; error_out: diff --git a/common/src/tfe_ctrl_packet.cpp b/common/src/tfe_ctrl_packet.cpp index 0b6b3dc..4fed27f 100644 --- a/common/src/tfe_ctrl_packet.cpp +++ b/common/src/tfe_ctrl_packet.cpp @@ -34,7 +34,6 @@ void ctrl_packet_parser_init(struct ctrl_pkt_parser *handler) // return -1 : error int ctrl_packet_parser_parse(struct ctrl_pkt_parser *handler, const char *data, size_t length) { - // TODO FREE return parse_messagepack(data, length, handler); } @@ -59,14 +58,6 @@ void ctrl_packet_parser_dump(struct ctrl_pkt_parser *handler) { TFE_LOG_INFO(g_default_logger, "%s: %d sce policy_ids[%03lu]", LOG_TAG_POLICY, i, handler->sce_policy_ids[i]); } - - uint64_t policy_id = 0; - tfe_cmsg_get_value(handler->cmsg, TFE_CMSG_POLICY_ID, (unsigned char *)&policy_id, 64, &cmsg_len); - TFE_LOG_INFO(g_default_logger, "TFE_CMSG_POLICY_ID: %lu", policy_id); - uint16_t client_mss = 0; - tfe_cmsg_get_value(handler->cmsg, TFE_CMSG_TCP_RESTORE_MSS_CLIENT, (unsigned char *)&client_mss, 16, &cmsg_len); - TFE_LOG_INFO(g_default_logger, "TFE_CMSG_TCP_RESTORE_MSS_CLIENT: %u", client_mss); - } } diff --git a/common/src/tfe_metrics.cpp b/common/src/tfe_metrics.cpp index 735a0a8..aebf9dd 100644 --- a/common/src/tfe_metrics.cpp +++ b/common/src/tfe_metrics.cpp @@ -19,7 +19,6 @@ enum SCE_STAT_FIELD STAT_RAW_PKT_TX_PKT, STAT_RAW_PKT_TX_B, - // steering STAT_DECRYPTED_TX_PKT, STAT_DECRYPTED_TX_B, STAT_DECRYPTED_RX_PKT, @@ -28,6 +27,23 @@ enum SCE_STAT_FIELD // control packet STAT_CONTROL_RX_PKT, STAT_CONTROL_RX_B, + STAT_CONTROL_TX_PKT, + STAT_CONTROL_TX_B, + + STAT_TAP_RX_PKT, + STAT_TAP_RX_B, + STAT_TAP_TX_PKT, + STAT_TAP_TX_B, + + STAT_TAP_C_RX_PKT, + STAT_TAP_C_RX_B, + STAT_TAP_C_TX_PKT, + STAT_TAP_C_TX_B, + + STAT_TAP_S_RX_PKT, + STAT_TAP_S_RX_B, + STAT_TAP_S_TX_PKT, + STAT_TAP_S_TX_B, STAT_CTRL_PKT_OPENING, STAT_CTRL_PKT_ACTIVE, @@ -64,6 +80,22 @@ static const char *stat_map[] = // control packet [STAT_CONTROL_RX_PKT] = "ctrl_rx_pkt", [STAT_CONTROL_RX_B] = "ctrl_rx_B", + [STAT_CONTROL_TX_PKT] = "ctrl_tx_pkt", + [STAT_CONTROL_TX_B] = "ctrl_tx_B", + + // tap packet + [STAT_TAP_RX_PKT] = "tap_rx_pkt", + [STAT_TAP_RX_B] = "tap_rx_B", + [STAT_TAP_TX_PKT] = "tap_tx_pkt", + [STAT_TAP_TX_B] = "tap_tx_B", + [STAT_TAP_C_RX_PKT] = "tap_c_rx_pkt", + [STAT_TAP_C_RX_B] = "tap_c_rx_B", + [STAT_TAP_C_TX_PKT] = "tap_c_tx_pkt", + [STAT_TAP_C_TX_B] = "tap_c_tx_B", + [STAT_TAP_S_RX_PKT] = "tap_s_rx_pkt", + [STAT_TAP_S_RX_B] = "tap_s_rx_B", + [STAT_TAP_S_TX_PKT] = "tap_s_tx_pkt", + [STAT_TAP_S_TX_B] = "tap_s_tx_B", [STAT_CTRL_PKT_OPENING] = "ctrl_pkt_open", [STAT_CTRL_PKT_ACTIVE] = "ctrl_pkt_avtive", @@ -94,7 +126,6 @@ void global_metrics_destory(struct global_metrics *metrics) { if (metrics) { - FS_library_destroy(); free(metrics); metrics = NULL; } @@ -121,6 +152,18 @@ void global_metrics_dump(struct global_metrics *metrics) // control packet FS_operate(metrics->fs_handle, metrics->fs_id[STAT_CONTROL_RX_PKT], 0, FS_OP_SET, __atomic_fetch_add(&(metrics->ctrl_pkt_rx.n_pkts), 0, __ATOMIC_RELAXED)); FS_operate(metrics->fs_handle, metrics->fs_id[STAT_CONTROL_RX_B], 0, FS_OP_SET, __atomic_fetch_add(&(metrics->ctrl_pkt_rx.n_bytes), 0, __ATOMIC_RELAXED)); + FS_operate(metrics->fs_handle, metrics->fs_id[STAT_CONTROL_RX_PKT], 0, FS_OP_SET, __atomic_fetch_add(&(metrics->ctrl_pkt_rx.n_pkts), 0, __ATOMIC_RELAXED)); + FS_operate(metrics->fs_handle, metrics->fs_id[STAT_CONTROL_RX_B], 0, FS_OP_SET, __atomic_fetch_add(&(metrics->ctrl_pkt_rx.n_bytes), 0, __ATOMIC_RELAXED)); + + FS_operate(metrics->fs_handle, metrics->fs_id[STAT_TAP_RX_PKT], 0, FS_OP_SET, __atomic_fetch_add(&(metrics->ctrl_pkt_rx.n_pkts), 0, __ATOMIC_RELAXED)); + FS_operate(metrics->fs_handle, metrics->fs_id[STAT_TAP_RX_B], 0, FS_OP_SET, __atomic_fetch_add(&(metrics->ctrl_pkt_rx.n_bytes), 0, __ATOMIC_RELAXED)); + FS_operate(metrics->fs_handle, metrics->fs_id[STAT_TAP_TX_PKT], 0, FS_OP_SET, __atomic_fetch_add(&(metrics->ctrl_pkt_rx.n_pkts), 0, __ATOMIC_RELAXED)); + FS_operate(metrics->fs_handle, metrics->fs_id[STAT_TAP_TX_B], 0, FS_OP_SET, __atomic_fetch_add(&(metrics->ctrl_pkt_rx.n_bytes), 0, __ATOMIC_RELAXED)); + + FS_operate(metrics->fs_handle, metrics->fs_id[STAT_TAP_C_RX_PKT], 0, FS_OP_SET, __atomic_fetch_add(&(metrics->ctrl_pkt_rx.n_pkts), 0, __ATOMIC_RELAXED)); + FS_operate(metrics->fs_handle, metrics->fs_id[STAT_TAP_C_RX_B], 0, FS_OP_SET, __atomic_fetch_add(&(metrics->ctrl_pkt_rx.n_bytes), 0, __ATOMIC_RELAXED)); + FS_operate(metrics->fs_handle, metrics->fs_id[STAT_TAP_C_TX_PKT], 0, FS_OP_SET, __atomic_fetch_add(&(metrics->ctrl_pkt_rx.n_pkts), 0, __ATOMIC_RELAXED)); + FS_operate(metrics->fs_handle, metrics->fs_id[STAT_TAP_C_TX_B], 0, FS_OP_SET, __atomic_fetch_add(&(metrics->ctrl_pkt_rx.n_bytes), 0, __ATOMIC_RELAXED)); FS_operate(metrics->fs_handle, metrics->fs_id[STAT_CTRL_PKT_OPENING], 0, FS_OP_SET, __atomic_fetch_add(&(metrics->ctrl_pkt_opening_num), 0, __ATOMIC_RELAXED)); FS_operate(metrics->fs_handle, metrics->fs_id[STAT_CTRL_PKT_ACTIVE], 0, FS_OP_SET, __atomic_fetch_add(&(metrics->ctrl_pkt_active_num), 0, __ATOMIC_RELAXED)); diff --git a/common/src/tfe_mpack.cpp b/common/src/tfe_mpack.cpp index 73a7b89..f6bb9ec 100644 --- a/common/src/tfe_mpack.cpp +++ b/common/src/tfe_mpack.cpp @@ -16,18 +16,30 @@ enum ctr_pkt_index INDEX_SESSION_ID, INDEX_STATE, INDEX_METHOD, - INDEX_SCE, - INDEX_SHAPER, - INDEX_PROXY, + INDEX_KEY_SCE, + INDEX_VALUE_SCE, + INDEX_KEY_SHAPER, + INDEX_VALUE_SHAPER, + INDEX_KEY_PROXY, + INDEX_VALUE_PROXY, INDEX_MAX }; +enum { + MPACK_ARRAY_FQDN_IDS, + MPACK_ARRAY_SEQ_SIDS, + MPACK_ARRAY_ACK_SIDS, + MPACK_ARRAY_SEQ_ROUTE_CTX, + MPACK_ARRAY_ACK_ROUTE_CTX, +}; + struct mpack_mmap_id2type { int id; enum tfe_cmsg_tlv_type type; char *str_name; int size; + int array_index; }mpack_table[] = { {.id = 0, .type = TFE_CMSG_POLICY_ID, .str_name = "TFE_CMSG_POLICY_ID", .size = 8}, {.id = 1, .type = TFE_CMSG_TCP_RESTORE_SEQ, .str_name = "TFE_CMSG_TCP_RESTORE_SEQ", .size = 4}, @@ -61,49 +73,116 @@ struct mpack_mmap_id2type {.id = 29, .type = TFE_CMSG_SRC_IP_LOCATION_SUBDIVISION, .str_name = "TFE_CMSG_SRC_IP_LOCATION_SUBDIVISION", .size = 256}, {.id = 30, .type = TFE_CMSG_DST_IP_LOCATION_SUBDIVISION, .str_name = "TFE_CMSG_DST_IP_LOCATION_SUBDIVISION", .size = 256}, {.id = 31, .type = TFE_CMSG_SSL_CLIENT_JA3_FINGERPRINT, .str_name = "TFE_CMSG_SSL_CLIENT_JA3_FINGERPRINT", .size = 32}, - {.id = 32, .type = TFE_CMSG_FQDN_CAT_ID_VAL, .str_name = "TFE_CMSG_FQDN_CAT_ID_VAL", .size = 4} + {.id = 32, .type = TFE_CMSG_FQDN_CAT_ID_VAL, .str_name = "TFE_CMSG_FQDN_CAT_ID_VAL", .size = 4, .array_index = MPACK_ARRAY_FQDN_IDS}, + {.id = 33, .str_name = "TFE_SEQ_SIDS", .size = 2, .array_index = MPACK_ARRAY_SEQ_SIDS}, + {.id = 34, .str_name = "TFE_ACK_SIDS", .size = 2, .array_index = MPACK_ARRAY_ACK_SIDS}, + {.id = 35, .str_name = "TFE_SEQ_ROUTE_CTX", .size = 1, .array_index = MPACK_ARRAY_SEQ_ROUTE_CTX}, + {.id = 36, .str_name = "TFE_ACK_ROUTE_CTX", .size = 1, .array_index = MPACK_ARRAY_ACK_ROUTE_CTX} }; -static int proxy_parse_messagepack(msgpack_object obj, void *ctx) +static void fqdn_id_set_cmsg(struct ctrl_pkt_parser *handler, msgpack_object *ptr, int table_index) { - struct ctrl_pkt_parser *handler = (struct ctrl_pkt_parser *)ctx; uint32_t fqdn_val[8] = {0}; - for (unsigned int i = 0; i < obj.via.array.size; i++) { - msgpack_object ptr = obj.via.array.ptr[i]; + tfe_cmsg_set(handler->cmsg, TFE_CMSG_FQDN_CAT_ID_NUM, (const unsigned char *)&ptr->via.array.size, sizeof(uint32_t)); + TFE_LOG_DEBUG(g_default_logger, "%s: session: %lu array fqdn_id num: [%u]", LOG_TAG_CTRLPKT, handler->session_id, ptr->via.array.size); + for (uint32_t j = 0; j < ptr->via.array.size; j++) { + fqdn_val[j] = ptr->via.array.ptr[j].via.u64; + TFE_LOG_DEBUG(g_default_logger, "%s: session: %lu array fqdn_id msgpack cmsg: [%s] -> [%lu]", LOG_TAG_CTRLPKT, handler->session_id, mpack_table[table_index].str_name, ptr->via.array.ptr[j].via.u64); + } + tfe_cmsg_set(handler->cmsg ,TFE_CMSG_FQDN_CAT_ID_VAL, (const unsigned char*)fqdn_val, ptr->via.array.size * sizeof(uint32_t)); + return; +} + +static void sids_array_parse_mpack(struct ctrl_pkt_parser *handler, msgpack_object *ptr, int table_index, int is_seq) +{ + struct sids *sid= is_seq ? &handler->seq_sids : &handler->ack_sids; + + sid->num = ptr->via.array.size > MR_SID_LIST_MAXLEN ? MR_SID_LIST_MAXLEN : ptr->via.array.size; + TFE_LOG_DEBUG(g_default_logger, "%s: session: %lu array sids num: [%u]", LOG_TAG_CTRLPKT, handler->session_id, ptr->via.array.size); + for (int i = 0; i < sid->num; i++) + { + sid->elems[i] = ptr->via.array.ptr[i].via.u64; + } + return; +} + +static void route_ctx_parse_mpack(struct ctrl_pkt_parser *handler, msgpack_object *ptr, int table_index, int is_seq) +{ + struct route_ctx *ctx = is_seq ? &handler->seq_route_ctx : &handler->ack_route_ctx; + + ctx->len = ptr->via.array.size > 64 ? 64 : ptr->via.array.size; + TFE_LOG_DEBUG(g_default_logger, "%s: session: %lu array route ctx num: [%u]", LOG_TAG_CTRLPKT, handler->session_id, ptr->via.array.size); + for (int i = 0; i < ctx->len; i++) + { + memcpy(ctx->data+i, &ptr->via.array.ptr[i].via.u64, 1); + } + return; +} + +static int proxy_parse_messagepack(msgpack_object *obj, void *ctx) +{ + struct ctrl_pkt_parser *handler = (struct ctrl_pkt_parser *)ctx; + + for (unsigned int i = 0; i < obj->via.array.size; i++) { + msgpack_object *ptr = &obj->via.array.ptr[i]; if (i == 0) { - if (ptr.type == MSGPACK_OBJECT_ARRAY) { - handler->tfe_policy_id_num = ptr.via.array.size; - for (uint32_t j = 0; j < ptr.via.array.size; j++) { - handler->tfe_policy_ids[j] = ptr.via.array.ptr[j].via.u64; + if (ptr->type == MSGPACK_OBJECT_ARRAY) { + handler->tfe_policy_id_num = ptr->via.array.size; + for (uint32_t j = 0; j < ptr->via.array.size; j++) { + handler->tfe_policy_ids[j] = ptr->via.array.ptr[j].via.u64; } tfe_cmsg_set(handler->cmsg, mpack_table[i].type, (const unsigned char *)&handler->tfe_policy_ids[0], sizeof(uint64_t)); - TFE_LOG_DEBUG(g_default_logger, "%s: interger msgpack cmsg: [%s] num: [%d]", LOG_TAG_CTRLPKT, mpack_table[i].str_name, handler->tfe_policy_id_num); + TFE_LOG_DEBUG(g_default_logger, "%s: session: %lu interger msgpack cmsg: [%s] num: [%d]", LOG_TAG_CTRLPKT, handler->session_id, mpack_table[i].str_name, handler->tfe_policy_id_num); for (int j = 0; j < handler->tfe_policy_id_num; j++) { - TFE_LOG_DEBUG(g_default_logger, "%s: policy id:%lu ", LOG_TAG_CTRLPKT, handler->tfe_policy_ids[j]); + TFE_LOG_DEBUG(g_default_logger, "%s: session: %lu policy id:%lu ", LOG_TAG_CTRLPKT, handler->session_id, handler->tfe_policy_ids[j]); } } continue; } - switch (ptr.type) { + switch (ptr->type) { case MSGPACK_OBJECT_POSITIVE_INTEGER: - tfe_cmsg_set(handler->cmsg, mpack_table[i].type, (const unsigned char *)&ptr.via.u64, mpack_table[i].size); - TFE_LOG_DEBUG(g_default_logger, "%s: interger msgpack cmsg: [%s] -> [%lu]", LOG_TAG_CTRLPKT, mpack_table[i].str_name, ptr.via.u64); + // TFE_CMSG_TCP_RESTORE_PROTOCOL tsg master 发送数据错误,临时强制设置为1 + if (i == 11) + { + uint8_t protocol = 1; + tfe_cmsg_set(handler->cmsg, TFE_CMSG_TCP_RESTORE_PROTOCOL, (const unsigned char *)&protocol, 1); + } + else + { + tfe_cmsg_set(handler->cmsg, mpack_table[i].type, (const unsigned char *)&ptr->via.u64, mpack_table[i].size); + } + TFE_LOG_DEBUG(g_default_logger, "%s: session: %lu interger msgpack cmsg: [%s] -> [%lu]", LOG_TAG_CTRLPKT, handler->session_id, mpack_table[i].str_name, ptr->via.u64); break; case MSGPACK_OBJECT_STR: - tfe_cmsg_set(handler->cmsg, mpack_table[i].type, (const unsigned char *)ptr.via.str.ptr, ptr.via.str.size); - TFE_LOG_DEBUG(g_default_logger, "%s: string msgpack cmsg: [%s] -> [%s]", LOG_TAG_CTRLPKT, mpack_table[i].str_name, ptr.via.str.ptr); + tfe_cmsg_set(handler->cmsg, mpack_table[i].type, (const unsigned char *)ptr->via.str.ptr, ptr->via.str.size); + TFE_LOG_DEBUG(g_default_logger, "%s: session: %lu string msgpack cmsg: [%s] -> [%s]", LOG_TAG_CTRLPKT, handler->session_id, mpack_table[i].str_name, ptr->via.str.ptr); + break; + case MSGPACK_OBJECT_NIL: + TFE_LOG_DEBUG(g_default_logger, "%s: session: %lu msgpack cmsg: [%s] -> [nil]", LOG_TAG_CTRLPKT, handler->session_id, mpack_table[i].str_name); break; case MSGPACK_OBJECT_ARRAY: - if (i == 32) { - tfe_cmsg_set(handler->cmsg, TFE_CMSG_FQDN_CAT_ID_NUM, (const unsigned char *)&ptr.via.array.size, sizeof(uint32_t)); - for (uint32_t j = 0; j < ptr.via.array.size; j++) { - fqdn_val[j] = ptr.via.array.ptr[j].via.u64; - TFE_LOG_DEBUG(g_default_logger, "%s: array msgpack cmsg: [%s] -> [%lu]", LOG_TAG_CTRLPKT, mpack_table[i].str_name, ptr.via.array.ptr[j].via.u64); - } - tfe_cmsg_set(handler->cmsg ,TFE_CMSG_FQDN_CAT_ID_VAL, (const unsigned char*)fqdn_val, ptr.via.array.size * sizeof(uint32_t)); + switch(mpack_table[i].array_index) + { + case MPACK_ARRAY_FQDN_IDS: + fqdn_id_set_cmsg(handler, ptr, i); + break; + case MPACK_ARRAY_SEQ_SIDS: + sids_array_parse_mpack(handler, ptr, i, 1); + break; + case MPACK_ARRAY_ACK_SIDS: + sids_array_parse_mpack(handler, ptr, i, 0); + break; + case MPACK_ARRAY_SEQ_ROUTE_CTX: + route_ctx_parse_mpack(handler, ptr, i, 1); + break; + case MPACK_ARRAY_ACK_ROUTE_CTX: + route_ctx_parse_mpack(handler, ptr, i, 0); + break; + default: + break; } break; default: @@ -117,6 +196,8 @@ int parse_messagepack(const char* data, size_t length, void *ctx) { struct ctrl_pkt_parser *handler = (struct ctrl_pkt_parser *)ctx; size_t off = 0; + msgpack_object *obj = NULL; + msgpack_object *ptr = NULL; msgpack_unpacked unpacked; msgpack_unpacked_init(&unpacked); @@ -124,93 +205,91 @@ int parse_messagepack(const char* data, size_t length, void *ctx) msgpack_unpack_return ret = msgpack_unpack_next(&unpacked, data, length, &off); if (ret != MSGPACK_UNPACK_SUCCESS) { TFE_LOG_DEBUG(g_default_logger, "%s: unexpected control packet: data[%s]", LOG_TAG_CTRLPKT, data); - return -1; + goto end; } - msgpack_object obj = unpacked.data; - if (obj.type != MSGPACK_OBJECT_ARRAY || obj.via.array.size < INDEX_PROXY) { - // TFE_LOG_DEBUG(g_default_logger, "%s: unexpected control packet: msgpack type[%02x], array size:%d", LOG_TAG_CTRLPKT, obj.type, obj.via.array.size); - return -1; + obj = &unpacked.data; + if (obj->type != MSGPACK_OBJECT_ARRAY) { + TFE_LOG_DEBUG(g_default_logger, "%s: unexpected control packet: msgpack type is not MSGPACK_OBJECT_ARRAY", LOG_TAG_CTRLPKT); + goto end; } - for (unsigned int i = 0; i < obj.via.array.size; i++) { - msgpack_object ptr = obj.via.array.ptr[i]; + for (unsigned int i = 0; i < obj->via.array.size; i++) { + ptr = &obj->via.array.ptr[i]; switch (i) { case INDEX_TSYNC: - if (ptr.type == MSGPACK_OBJECT_STR) { - memcpy(handler->tsync, ptr.via.str.ptr, ptr.via.str.size); + if (ptr->type == MSGPACK_OBJECT_STR) { + memcpy(handler->tsync, ptr->via.str.ptr, ptr->via.str.size); } else { - TFE_LOG_DEBUG(g_default_logger, "%s: unexpected control packet: (invalid tsync type) %02x", LOG_TAG_CTRLPKT, ptr.type); + TFE_LOG_DEBUG(g_default_logger, "%s: unexpected control packet: (invalid tsync type) %02x", LOG_TAG_CTRLPKT, ptr->type); } break; case INDEX_SESSION_ID: - if (ptr.type == MSGPACK_OBJECT_STR) { - char session_id[64] = {0}; - memcpy(session_id, ptr.via.str.ptr, ptr.via.str.size); - handler->session_id = atoll(session_id); + if (ptr->type == MSGPACK_OBJECT_POSITIVE_INTEGER) { + handler->session_id = ptr->via.u64; } else { - TFE_LOG_DEBUG(g_default_logger, "%s: unexpected control packet: (invalid session id type) %02x", LOG_TAG_CTRLPKT, ptr.type); + TFE_LOG_DEBUG(g_default_logger, "%s: unexpected control packet: (invalid session id type) %02x", LOG_TAG_CTRLPKT, ptr->type); } break; case INDEX_STATE: - if (ptr.type == MSGPACK_OBJECT_STR) { - if (strncasecmp(ptr.via.str.ptr, "opening", ptr.via.str.size) == 0) + if (ptr->type == MSGPACK_OBJECT_STR) { + if (strncasecmp(ptr->via.str.ptr, "opening", ptr->via.str.size) == 0) { handler->state = SESSION_STATE_OPENING; } - else if (strncasecmp(ptr.via.str.ptr, "active", ptr.via.str.size) == 0) + else if (strncasecmp(ptr->via.str.ptr, "active", ptr->via.str.size) == 0) { handler->state = SESSION_STATE_ACTIVE; } - else if (strncasecmp(ptr.via.str.ptr, "closing", ptr.via.str.size) == 0) + else if (strncasecmp(ptr->via.str.ptr, "closing", ptr->via.str.size) == 0) { handler->state = SESSION_STATE_CLOSING; } - else if (strncasecmp(ptr.via.str.ptr, "resetall", ptr.via.str.size) == 0) + else if (strncasecmp(ptr->via.str.ptr, "resetall", ptr->via.str.size) == 0) { handler->state = SESSION_STATE_RESETALL; } else { - TFE_LOG_DEBUG(g_default_logger, "%s: unexpected control packet: (invalid state value) %s", LOG_TAG_CTRLPKT, ptr.via.str.ptr); + TFE_LOG_DEBUG(g_default_logger, "%s: unexpected control packet: (invalid state value) %s", LOG_TAG_CTRLPKT, ptr->via.str.ptr); } } else { - TFE_LOG_DEBUG(g_default_logger, "%s: unexpected control packet: (invalid state type) %02x", LOG_TAG_CTRLPKT, ptr.type); + TFE_LOG_DEBUG(g_default_logger, "%s: unexpected control packet: (invalid state type) %02x", LOG_TAG_CTRLPKT, ptr->type); } break; case INDEX_METHOD: - if (ptr.type == MSGPACK_OBJECT_STR) { - memcpy(handler->method, ptr.via.str.ptr, ptr.via.str.size); + if (ptr->type == MSGPACK_OBJECT_STR) { + memcpy(handler->method, ptr->via.str.ptr, ptr->via.str.size); } else { - TFE_LOG_DEBUG(g_default_logger, "%s: unexpected control packet: (invalid method type) %02x", LOG_TAG_CTRLPKT, ptr.type); + TFE_LOG_DEBUG(g_default_logger, "%s: unexpected control packet: (invalid method type) %02x", LOG_TAG_CTRLPKT, ptr->type); } break; - case INDEX_SCE: - if (ptr.type == MSGPACK_OBJECT_ARRAY) { - msgpack_object rule_id = ptr.via.array.ptr[0]; + case INDEX_VALUE_SCE: + if (ptr->type == MSGPACK_OBJECT_ARRAY) { + msgpack_object rule_id = ptr->via.array.ptr[0]; handler->sce_policy_id_num = rule_id.via.array.size; for (uint32_t j = 0; j < rule_id.via.array.size; j++) { handler->sce_policy_ids[j] = rule_id.via.array.ptr[j].via.u64; } } break; - case INDEX_SHAPER: - break; - case INDEX_PROXY: - if (ptr.type == MSGPACK_OBJECT_ARRAY) { + case INDEX_VALUE_PROXY: + if (ptr->type == MSGPACK_OBJECT_ARRAY) { proxy_parse_messagepack(ptr, handler); } else { - TFE_LOG_DEBUG(g_default_logger, "%s: unexpected control packet: (invalid proxy type) %02x", LOG_TAG_CTRLPKT, ptr.type); + TFE_LOG_DEBUG(g_default_logger, "%s: unexpected control packet: (invalid proxy type) %02x", LOG_TAG_CTRLPKT, ptr->type); } break; default: break; } } +end: + msgpack_unpacked_destroy(&unpacked); return 0; } diff --git a/common/src/tfe_packet_io.cpp b/common/src/tfe_packet_io.cpp index e3fb77b..17ed84b 100644 --- a/common/src/tfe_packet_io.cpp +++ b/common/src/tfe_packet_io.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -23,6 +24,7 @@ #include "tfe_tap_rss.h" #include +#include /* * add: vxlan_hdr * del: marsio_buff_ctrlzone_reset() @@ -46,8 +48,8 @@ #define RX_BURST_MAX 128 #define TRAFFIC_IS_DECRYPTED (1 << 0) -#define SET_TRAFFIC_IS_DECRYPTED(field) (field || TRAFFIC_IS_DECRYPTED) -#define CLEAR_TRAFFIC_IS_DECRYPTED(field) (field && ~TRAFFIC_IS_DECRYPTED) +#define SET_TRAFFIC_IS_DECRYPTED(field) (field | TRAFFIC_IS_DECRYPTED) +#define CLEAR_TRAFFIC_IS_DECRYPTED(field) (field & ~TRAFFIC_IS_DECRYPTED) struct config { @@ -91,850 +93,22 @@ enum inject_pkt_action struct metadata { + int write_ref; uint64_t session_id; char *raw_data; int raw_len; - int dir_is_e2i; + int is_e2i_dir; int is_ctrl_pkt; - uint16_t l7_offset; // only control packet set l7_offset - uint16_t user_field; // only raw packet set traffic_is_decrypted (1 << 0) + uint16_t l7offset; // only control packet set l7offset + uint16_t is_decrypted; struct sids sids; struct route_ctx route_ctx; }; -/****************************************************************************** - * API Declaration - ******************************************************************************/ - -struct packet_io *packet_io_create(const char *profile, int thread_num, cpu_set_t *coremask); -void packet_io_destory(struct packet_io *handle); - -int packet_io_polling_nf_interface(struct packet_io *handle, int thread_seq, void *ctx); - -extern int tcp_policy_enforce(struct tcp_policy_enforcer *tcp_enforcer, struct tfe_cmsg *cmsg); -extern int tfe_proxy_fds_accept(struct tfe_proxy * ctx, int fd_downstream, int fd_upstream, int fd_fake_c, int fd_fake_s, struct tfe_cmsg * cmsg); - -extern void chaining_policy_enforce(struct chaining_policy_enforcer *enforcer, struct tfe_cmsg *cmsg, uint64_t rule_id); - -// return 0 : success -// return -1 : error -static int packet_io_config(const char *profile, struct config *config); - -// return 0 : success -// return -1 : error -static int packet_io_get_metadata(marsio_buff_t *tx_buff, struct metadata *meta); -// return 0 : success -// return -1 : error -static int packet_io_set_metadata(marsio_buff_t *tx_buff, struct metadata *meta); -static void packet_io_dump_metadata(marsio_buff_t *tx_buff, struct metadata *meta); - -// return 0 : success -// return -1 : error -static int handle_control_packet(struct packet_io *handle, marsio_buff_t *rx_buff, int thread_seq, void *ctx); - -static int handle_raw_packet_from_nf(struct packet_io *handle, marsio_buff_t *rx_buff, int thread_seq, void *ctx, int *action_bytes); - -static int add_ether_header(void *raw_data, char *src_mac, char *dst_mac){ - struct ethhdr *ether_hdr = (struct ethhdr*)raw_data; - memcpy(ether_hdr->h_dest, dst_mac, sizeof(ether_hdr->h_dest)); - memcpy(ether_hdr->h_source, src_mac, sizeof(ether_hdr->h_source)); - return 0; -} - -// return 0 : success -// return -1 : error -static int handle_session_opening(struct metadata *meta, struct ctrl_pkt_parser *parser, int thread_seq, void *ctx); -// return 0 : success -// return -1 : error -static int handle_session_closing(struct metadata *meta, struct ctrl_pkt_parser *parser, int thread_seq, void *ctx); -// return 0 : success -// return -1 : error -static int handle_session_active(struct metadata *meta, struct ctrl_pkt_parser *parser, int thread_seq, void *ctx); -// return 0 : success -// return -1 : error -static int handle_session_resetall(struct metadata *meta, struct ctrl_pkt_parser *parser, int thread_seq, void *ctx); - -static void session_value_free_cb(void *ctx); - -// return 0 : not keepalive packet -// return 1 : is keepalive packet -static int is_downstream_keepalive_packet(marsio_buff_t *rx_buff); - - -/****************************************************************************** - * API Definition - ******************************************************************************/ - -struct packet_io *packet_io_create(const char *profile, int thread_num, cpu_set_t *coremask) -{ - int opt = 1; - struct packet_io *handle = (struct packet_io *)calloc(1, sizeof(struct packet_io)); - assert(handle != NULL); - handle->thread_num = thread_num; - - if (packet_io_config(profile, &(handle->config)) != 0) - { - goto error_out; - } - - handle->instance = marsio_create(); - if (handle->instance == NULL) - { - TFE_LOG_ERROR(g_default_logger, "%s: unable to create marsio instance", LOG_TAG_PKTIO); - goto error_out; - } - - if (marsio_option_set(handle->instance, MARSIO_OPT_THREAD_MASK_IN_CPUSET, coremask, sizeof(cpu_set_t)) != 0) - { - TFE_LOG_ERROR(g_default_logger, "%s: unable to set MARSIO_OPT_EXIT_WHEN_ERR option for marsio instance", LOG_TAG_PKTIO); - goto error_out; - } - - if (marsio_option_set(handle->instance, MARSIO_OPT_EXIT_WHEN_ERR, &opt, sizeof(opt)) != 0) - { - TFE_LOG_ERROR(g_default_logger, "%s: unable to set MARSIO_OPT_EXIT_WHEN_ERR option for marsio instance", LOG_TAG_PKTIO); - goto error_out; - } - - if (marsio_init(handle->instance, handle->config.app_symbol) != 0) - { - TFE_LOG_ERROR(g_default_logger, "%s: unable to initialize marsio instance", LOG_TAG_PKTIO); - goto error_out; - } - - // Netwrok Function Interface - handle->dev_nf_interface.mr_dev = marsio_open_device(handle->instance, handle->config.dev_nf_interface, handle->thread_num, handle->thread_num); - if (handle->dev_nf_interface.mr_dev == NULL) - { - TFE_LOG_ERROR(g_default_logger, "%s: unable to open device %s", LOG_TAG_PKTIO, handle->config.dev_nf_interface); - goto error_out; - } - - handle->dev_nf_interface.mr_path = marsio_sendpath_create_by_vdev(handle->dev_nf_interface.mr_dev); - if (handle->dev_nf_interface.mr_path == NULL) - { - TFE_LOG_ERROR(g_default_logger, "%s: unable to create sendpath for device %s", LOG_TAG_PKTIO, handle->config.dev_nf_interface); - goto error_out; - } - - return handle; - -error_out: - packet_io_destory(handle); - return NULL; -} - -void packet_io_destory(struct packet_io *handle) -{ - if (handle) - { - if (handle->dev_nf_interface.mr_path) - { - marsio_sendpath_destory(handle->dev_nf_interface.mr_path); - handle->dev_nf_interface.mr_path = NULL; - } - - if (handle->dev_nf_interface.mr_dev) - { - marsio_close_device(handle->dev_nf_interface.mr_dev); - handle->dev_nf_interface.mr_dev = NULL; - } - - if (handle->instance) - { - marsio_destory(handle->instance); - handle->instance = NULL; - } - - free(handle); - handle = NULL; - } -} - -int packet_io_thread_init(struct packet_io *handle, struct acceptor_thread_ctx *thread_ctx) -{ - if (marsio_thread_init(handle->instance) != 0) - { - TFE_LOG_ERROR(g_default_logger, "%s: unable to init marsio thread %d", LOG_TAG_PKTIO, thread_ctx->thread_index); - return -1; - } - - return 0; -} - -void packet_io_thread_wait(struct packet_io *handle, struct acceptor_thread_ctx *thread_ctx, int timeout_ms) -{ - struct mr_vdev *vdevs[] = {handle->dev_nf_interface.mr_dev}; - - marsio_poll_wait(handle->instance, vdevs, 1, thread_ctx->thread_index, timeout_ms); -} - -// return n_packet_recv -int packet_io_polling_nf_interface(struct packet_io *handle, int thread_seq, void *ctx) -{ - struct acceptor_thread_ctx *thread = (struct acceptor_thread_ctx *)ctx; - struct global_metrics *g_metrics = thread->ref_metrics; - - marsio_buff_t *rx_buffs[RX_BURST_MAX]; - - // nr_recv <= rx_burst_max <= RX_BURST_MAX - int nr_recv = marsio_recv_burst(handle->dev_nf_interface.mr_dev, thread_seq, rx_buffs, handle->config.rx_burst_max); - if (nr_recv <= 0) - { - return 0; - } - - for (int j = 0; j < nr_recv; j++) - { - marsio_buff_t *rx_buff = rx_buffs[j]; - int raw_len = marsio_buff_datalen(rx_buff); - - if (is_downstream_keepalive_packet(rx_buff)) - { - marsio_send_burst(handle->dev_nf_interface.mr_path, thread_seq, &rx_buff, 1); - continue; - } - - if (marsio_buff_is_ctrlbuf(rx_buff)) - { - handle_control_packet(handle, rx_buff, thread_seq, ctx); - throughput_metrics_inc(&g_metrics->ctrl_pkt_rx, 1, raw_len); - // all control packet need bypass - marsio_send_burst(handle->dev_nf_interface.mr_path, thread_seq, &rx_buff, 1); - } - else - { - int action_bytes = 0; - handle_raw_packet_from_nf(handle, rx_buff, thread_seq, ctx, &action_bytes); - } - } - - return nr_recv; -} - -void handle_decryption_packet_from_tap(const char *data, int len, void *args) -{ - struct acceptor_thread_ctx *thread = (struct acceptor_thread_ctx *)args; - struct acceptor_ctx *acceptor_ctx = thread->ref_acceptor_ctx; - struct packet_io *packet_io = thread->ref_io; - - struct addr_tuple4 inner_addr; - struct raw_pkt_parser raw_parser; - memset(&inner_addr, 0, sizeof(struct addr_tuple4)); - raw_packet_parser_init(&raw_parser, 0, LAYER_TYPE_ALL, 8); - raw_packet_parser_parse(&raw_parser, (const void *)data, len); - raw_packet_parser_get_most_inner_tuple4(&raw_parser, &inner_addr); - - struct session_node *node = session_table_search_by_addr(thread->session_table, &inner_addr); - if (node == NULL) - { - char *addr_string = addr_tuple4_to_str(&inner_addr); - TFE_LOG_ERROR(g_default_logger, "%s: unexpected inject packet, unable to find session %s from session table, drop !!!", LOG_TAG_PKTIO, addr_string); - free(addr_string); - return; - } - struct session_ctx *s_ctx = (struct session_ctx *)node->val_data; - - marsio_buff_t *tx_buffs[1]; - int alloc_ret = marsio_buff_malloc_device(packet_io->dev_nf_interface.mr_dev, tx_buffs, 1, 0, thread->thread_index); - if (alloc_ret < 0){ - TFE_LOG_ERROR(g_default_logger, "Failed at alloc marsio buffer, ret = %d, thread_seq = %d", - alloc_ret, thread->thread_index); - return; - } - - char *dst = marsio_buff_append(tx_buffs[0], len); - memcpy(dst, data, len); - - struct metadata meta = {0}; - meta.session_id = s_ctx->session_id; - meta.raw_data = dst; - meta.raw_len = len; - meta.user_field = SET_TRAFFIC_IS_DECRYPTED(0); - meta.is_ctrl_pkt = 0; - meta.l7_offset = 0; - meta.sids.num = 1; - meta.sids.elems[0] = acceptor_ctx->sce_sids; - - if (memcmp(&inner_addr, &s_ctx->first_ctrl_pkt.tuple4, sizeof(struct addr_tuple4)) == 0) - meta.dir_is_e2i = s_ctx->first_ctrl_pkt.dir_is_e2i; - else - meta.dir_is_e2i = !s_ctx->first_ctrl_pkt.dir_is_e2i; - - if (meta.dir_is_e2i) - { - route_ctx_copy(&meta.route_ctx, &s_ctx->raw_pkt_e2i_route_ctx); - } - else - { - route_ctx_copy(&meta.route_ctx, &s_ctx->raw_pkt_i2e_route_ctx); - } - packet_io_set_metadata(tx_buffs[0], &meta); - marsio_send_burst(packet_io->dev_nf_interface.mr_path, thread->thread_index, tx_buffs, 1); -} - -void handle_raw_packet_from_tap(const char *data, int len, void *args) -{ - struct acceptor_thread_ctx *thread = (struct acceptor_thread_ctx *)args; - struct acceptor_ctx *acceptor_ctx = thread->ref_acceptor_ctx; - struct packet_io *packet_io = thread->ref_io; - - struct addr_tuple4 inner_addr; - struct raw_pkt_parser raw_parser; - memset(&inner_addr, 0, sizeof(struct addr_tuple4)); - raw_packet_parser_init(&raw_parser, 0, LAYER_TYPE_ALL, 8); - raw_packet_parser_parse(&raw_parser, (const void *)data, len); - raw_packet_parser_get_most_inner_tuple4(&raw_parser, &inner_addr); - - struct session_node *node = session_table_search_by_addr(thread->session_table, &inner_addr); - if (node == NULL) - { - char *addr_string = addr_tuple4_to_str(&inner_addr); - TFE_LOG_ERROR(g_default_logger, "%s: unexpected inject packet, unable to find session %s from session table, drop !!!", LOG_TAG_PKTIO, addr_string); - free(addr_string); - return; - } - struct session_ctx *s_ctx = (struct session_ctx *)node->val_data; - - marsio_buff_t *tx_buffs[1]; - int alloc_ret = marsio_buff_malloc_device(packet_io->dev_nf_interface.mr_dev, tx_buffs, 1, 0, thread->thread_index); - if (alloc_ret < 0){ - TFE_LOG_ERROR(g_default_logger, "Failed at alloc marsio buffer, ret = %d, thread_seq = %d", - alloc_ret, thread->thread_index); - return; - } - - char *dst = marsio_buff_append(tx_buffs[0], len + s_ctx->first_ctrl_pkt.header_len); - memcpy(dst, s_ctx->first_ctrl_pkt.header_data, s_ctx->first_ctrl_pkt.header_len); - memcpy(dst + s_ctx->first_ctrl_pkt.header_len, data, len); - - struct metadata meta = {0}; - meta.session_id = s_ctx->session_id; - meta.raw_data = dst; - meta.raw_len = len; - meta.user_field = s_ctx->user_field; - meta.is_ctrl_pkt = 0; - meta.l7_offset = 0; - - char *src_mac = NULL; - char *dst_mac = NULL; - if (memcmp(&inner_addr, &s_ctx->first_ctrl_pkt.tuple4, sizeof(struct addr_tuple4)) == 0) { - meta.dir_is_e2i = s_ctx->first_ctrl_pkt.dir_is_e2i; - struct ethhdr *ether_hdr = (struct ethhdr *)(s_ctx->first_ctrl_pkt.header_data); - src_mac = (char *)ether_hdr->h_source; - dst_mac = (char *)ether_hdr->h_dest; - } - else { - meta.dir_is_e2i = !s_ctx->first_ctrl_pkt.dir_is_e2i; - struct ethhdr *ether_hdr = (struct ethhdr *)(s_ctx->first_ctrl_pkt.header_data); - dst_mac = (char *)ether_hdr->h_source; - src_mac = (char *)ether_hdr->h_dest; - } - - if (meta.dir_is_e2i) - { - sids_copy(&meta.sids, &s_ctx->raw_pkt_e2i_sids); - route_ctx_copy(&meta.route_ctx, &s_ctx->raw_pkt_e2i_route_ctx); - } - else - { - sids_copy(&meta.sids, &s_ctx->raw_pkt_i2e_sids); - route_ctx_copy(&meta.route_ctx, &s_ctx->raw_pkt_i2e_route_ctx); - } - packet_io_set_metadata(tx_buffs[0], &meta); - // add_ether_header(dst, src_mac, dst_mac); - marsio_send_burst(packet_io->dev_nf_interface.mr_path, thread->thread_index, tx_buffs, 1); -} - -// return 0 : success -// return -1 : error -static int packet_io_config(const char *profile, struct config *config) -{ - MESA_load_profile_int_def(profile, "PACKET_IO", "rx_burst_max", (int *)&(config->rx_burst_max), 1); - MESA_load_profile_string_nodef(profile, "PACKET_IO", "app_symbol", config->app_symbol, sizeof(config->app_symbol)); - MESA_load_profile_string_nodef(profile, "PACKET_IO", "dev_nf_interface", config->dev_nf_interface, sizeof(config->dev_nf_interface)); - - if (config->rx_burst_max > RX_BURST_MAX) - { - TFE_LOG_ERROR(g_default_logger, "%s: invalid rx_burst_max, exceeds limit %d", LOG_TAG_PKTIO, RX_BURST_MAX); - return -1; - } - - if (strlen(config->app_symbol) == 0) - { - TFE_LOG_ERROR(g_default_logger, "%s: invalid app_symbol in %s", LOG_TAG_PKTIO, profile); - return -1; - } - - if (strlen(config->dev_nf_interface) == 0) - { - TFE_LOG_ERROR(g_default_logger, "%s: invalid dev_nf_interface in %s", LOG_TAG_PKTIO, profile); - return -1; - } - - TFE_LOG_DEBUG(g_default_logger, "%s: PACKET_IO->bypass_all_traffic : %d", LOG_TAG_PKTIO, config->bypass_all_traffic); - TFE_LOG_DEBUG(g_default_logger, "%s: PACKET_IO->rx_burst_max : %d", LOG_TAG_PKTIO, config->rx_burst_max); - TFE_LOG_DEBUG(g_default_logger, "%s: PACKET_IO->app_symbol : %s", LOG_TAG_PKTIO, config->app_symbol); - TFE_LOG_DEBUG(g_default_logger, "%s: PACKET_IO->dev_nf_interface : %s", LOG_TAG_PKTIO, config->dev_nf_interface); - - return 0; -} - -// return 0 : success -// return -1 : error -static int packet_io_get_metadata(marsio_buff_t *rx_buff, struct metadata *meta) -{ - memset(meta, 0, sizeof(struct metadata)); - - if (marsio_buff_get_metadata(rx_buff, MR_BUFF_SESSION_ID, &(meta->session_id), sizeof(meta->session_id)) <= 0) - { - TFE_LOG_ERROR(g_default_logger, "%s: unable to get session_id from metadata", LOG_TAG_PKTIO); - return -1; - } - - meta->raw_len = marsio_buff_datalen(rx_buff); - meta->raw_data = marsio_buff_mtod(rx_buff); - if (meta->raw_data == NULL || meta->raw_len == 0) - { - TFE_LOG_ERROR(g_default_logger, "%s: unable to get raw_data from metadata", LOG_TAG_PKTIO); - return -1; - } - - // 1: E2I - // 0: I2E - if (marsio_buff_get_metadata(rx_buff, MR_BUFF_DIR, &(meta->dir_is_e2i), sizeof(meta->dir_is_e2i)) <= 0) - { - TFE_LOG_ERROR(g_default_logger, "%s: unable to get buff_dir from metadata", LOG_TAG_PKTIO); - return -1; - } - - if (marsio_buff_is_ctrlbuf(rx_buff)) - { - meta->is_ctrl_pkt = 1; - // only control packet set MR_BUFF_PAYLOAD_OFFSET - if (marsio_buff_get_metadata(rx_buff, MR_BUFF_PAYLOAD_OFFSET, &(meta->l7_offset), sizeof(meta->l7_offset)) <= 0) - { - TFE_LOG_ERROR(g_default_logger, "%s: unable to get l7_offset from metadata", LOG_TAG_PKTIO); - return -1; - } - } - else - { - meta->is_ctrl_pkt = 0; - // if (marsio_buff_get_metadata(rx_buff, MR_BUFF_USER_0, &(meta->user_field), sizeof(meta->user_field)) <= 0) - // { - // TFE_LOG_ERROR(g_default_logger, "%s: unable to get user_field from metadata", LOG_TAG_PKTIO); - // return -1; - // } - } - - meta->route_ctx.len = marsio_buff_get_metadata(rx_buff, MR_BUFF_ROUTE_CTX, meta->route_ctx.data, sizeof(meta->route_ctx.data)); - if (meta->route_ctx.len <= 0) - { - TFE_LOG_ERROR(g_default_logger, "%s: unable to get route_ctx from metadata", LOG_TAG_PKTIO); - return -1; - } - - meta->sids.num = marsio_buff_get_sid_list(rx_buff, meta->sids.elems, sizeof(meta->sids.elems) / sizeof(meta->sids.elems[0])); - if (meta->sids.num < 0) - { - TFE_LOG_ERROR(g_default_logger, "%s: unable to get sid_list from metadata", LOG_TAG_PKTIO); - return -1; - } - - return 0; -} - -// return 0 : success -// return -1 : error -static int packet_io_set_metadata(marsio_buff_t *tx_buff, struct metadata *meta) -{ - if (meta->session_id) - { - if (marsio_buff_set_metadata(tx_buff, MR_BUFF_SESSION_ID, &(meta->session_id), sizeof(meta->session_id)) != 0) - { - TFE_LOG_ERROR(g_default_logger, "%s: unable to set session_id for metadata", LOG_TAG_PKTIO); - return -1; - } - } - - // 1: E2I - // 0: I2E -#if 0 - // use MR_BUFF_ROUTE_CTX instead - if (marsio_buff_set_metadata(tx_buff, MR_BUFF_DIR, &(meta->dir_is_e2i), sizeof(meta->dir_is_e2i)) != 0) - { - TFE_LOG_ERROR(g_default_logger, "%s: unable to set buff_dir for metadata", LOG_TAG_PKTIO); - return -1; - } -#endif - - if (meta->is_ctrl_pkt) - { - if (marsio_buff_set_metadata(tx_buff, MR_BUFF_PAYLOAD_OFFSET, &(meta->l7_offset), sizeof(meta->l7_offset)) != 0) - { - TFE_LOG_ERROR(g_default_logger, "%s: unable to set l7_offset for metadata", LOG_TAG_PKTIO); - return -1; - } - } - else - { - // if (marsio_buff_set_metadata(tx_buff, MR_BUFF_USER_0, &(meta->user_field), sizeof(meta->user_field)) != 0) - // { - // TFE_LOG_ERROR(g_default_logger, "%s: unable to set user_field for metadata", LOG_TAG_PKTIO); - // return -1; - // } - } - - if (meta->route_ctx.len > 0) - { - if (marsio_buff_set_metadata(tx_buff, MR_BUFF_ROUTE_CTX, meta->route_ctx.data, meta->route_ctx.len) != 0) - { - TFE_LOG_ERROR(g_default_logger, "%s: unable to set route_ctx for metadata", LOG_TAG_PKTIO); - return -1; - } - } - - if (meta->sids.num > 0) - { - if (marsio_buff_set_sid_list(tx_buff, meta->sids.elems, meta->sids.num) != 0) - { - TFE_LOG_ERROR(g_default_logger, "%s: unable to set sid_list for metadata", LOG_TAG_PKTIO); - return -1; - } - } - - return 0; -} - -static void packet_io_dump_metadata(marsio_buff_t *tx_buff, struct metadata *meta) -{ - TFE_LOG_DEBUG(g_default_logger, "%s: META={session_id: %lu, raw_len: %d, dir_is_e2i: %d, is_ctrl_pkt: %d, l7_offset: %d, user_field: %u, sids_num: %d}", LOG_TAG_PKTIO, meta->session_id, meta->raw_len, meta->dir_is_e2i, meta->is_ctrl_pkt, meta->l7_offset, meta->user_field, meta->sids.num); -} - -static int tcp_restore_set_from_cmsg(struct tfe_cmsg *cmsg, struct tcp_restore_info *restore_info) -{ - int ret = 0; - uint16_t length = 0; - - uint32_t seq; - ret = tfe_cmsg_get_value(cmsg, TFE_CMSG_TCP_RESTORE_SEQ, (unsigned char *)&seq, sizeof(uint32_t), &length); - if (ret == 0) - { - restore_info->client.seq = ntohl(seq); - restore_info->server.ack = ntohl(seq); - } - - uint32_t ack; - ret = tfe_cmsg_get_value(cmsg, TFE_CMSG_TCP_RESTORE_ACK, (unsigned char *)&ack, sizeof(uint32_t), &length); - if (ret == 0) - { - restore_info->client.ack = ntohl(ack); - restore_info->server.seq = ntohl(ack); - } - - uint8_t ts_client; - ret = tfe_cmsg_get_value(cmsg, TFE_CMSG_TCP_RESTORE_TS_CLIENT, (unsigned char *)&ts_client, sizeof(uint8_t), &length); - if (ret == 0) - { - restore_info->client.timestamp_perm = !!ts_client; - } - - uint8_t ts_server; - ret = tfe_cmsg_get_value(cmsg, TFE_CMSG_TCP_RESTORE_TS_SERVER, (unsigned char *)&ts_server, sizeof(uint8_t), &length); - if (ret == 0) - { - restore_info->server.timestamp_perm = !!ts_server; - } - - uint32_t ts_client_val; - ret = tfe_cmsg_get_value(cmsg, TFE_CMSG_TCP_RESTORE_TS_CLIENT_VAL, (unsigned char *)&ts_client_val, sizeof(uint32_t), &length); - if (ret == 0) - { - restore_info->client.ts_val = ntohl(ts_client_val); - } - - uint32_t ts_server_val; - ret = tfe_cmsg_get_value(cmsg, TFE_CMSG_TCP_RESTORE_TS_SERVER_VAL, (unsigned char *)&ts_server_val, sizeof(uint32_t), &length); - if (ret == 0) - { - restore_info->server.ts_val = ntohl(ts_server_val); - } - - uint8_t wsacle_client; - ret = tfe_cmsg_get_value(cmsg, TFE_CMSG_TCP_RESTORE_WSACLE_CLIENT, (unsigned char *)&wsacle_client, sizeof(uint8_t), &length); - if (ret == 0) - { - restore_info->client.wscale_perm = true; - restore_info->client.wscale = wsacle_client; - } - - uint8_t wsacle_server; - ret = tfe_cmsg_get_value(cmsg, TFE_CMSG_TCP_RESTORE_WSACLE_SERVER, (unsigned char *)&wsacle_server, sizeof(uint8_t), &length); - if (ret == 0) - { - restore_info->client.wscale_perm = true; - restore_info->client.wscale = wsacle_server; - } - - uint8_t sack_client; - ret = tfe_cmsg_get_value(cmsg, TFE_CMSG_TCP_RESTORE_SACK_CLIENT, (unsigned char *)&sack_client, sizeof(uint8_t), &length); - if (ret == 0) - { - restore_info->client.sack_perm = !!sack_client; - } - - uint8_t sack_server; - ret = tfe_cmsg_get_value(cmsg, TFE_CMSG_TCP_RESTORE_SACK_SERVER, (unsigned char *)&sack_server, sizeof(uint8_t), &length); - if (ret == 0) - { - restore_info->server.sack_perm = !!sack_server; - } - - uint16_t mss_client; - ret = tfe_cmsg_get_value(cmsg, TFE_CMSG_TCP_RESTORE_MSS_CLIENT, (unsigned char *)&mss_client, sizeof(uint16_t), &length); - if (ret == 0) - { - restore_info->client.mss = mss_client; - } - - uint16_t mss_server; - ret = tfe_cmsg_get_value(cmsg, TFE_CMSG_TCP_RESTORE_MSS_SERVER, (unsigned char *)&mss_server, sizeof(uint16_t), &length); - if (ret == 0) - { - restore_info->server.mss = mss_server; - } - - uint16_t window_client; - ret = tfe_cmsg_get_value(cmsg, TFE_CMSG_TCP_RESTORE_WINDOW_CLIENT, (unsigned char *)&window_client, sizeof(uint16_t), &length); - if (ret == 0) - { - restore_info->client.window = window_client; - } - - uint16_t window_server; - ret = tfe_cmsg_get_value(cmsg, TFE_CMSG_TCP_RESTORE_WINDOW_SERVER, (unsigned char *)&window_server, sizeof(uint16_t), &length); - if (ret == 0) - { - restore_info->server.window = window_server; - } - - uint8_t packet_cur_dir; - ret = tfe_cmsg_get_value(cmsg, TFE_CMSG_TCP_RESTORE_INFO_PACKET_CUR_DIR, (unsigned char *)&packet_cur_dir, sizeof(uint8_t), &length); - if (ret == 0) - { - restore_info->cur_dir = (enum tcp_restore_pkt_dir)packet_cur_dir; - } - - return 0; -} - -static int tcp_restore_set_from_pkg(struct addr_tuple4 *tuple4, struct tcp_restore_info *restore_info) -{ - if (tuple4->addr_type == ADDR_TUPLE4_TYPE_V4) - { - struct sockaddr_in *in_addr_client; - struct sockaddr_in *in_addr_server; - - if (restore_info->cur_dir == PKT_DIR_NOT_SET || restore_info->cur_dir == PKT_DIR_C2S) - { - in_addr_client = (struct sockaddr_in *)&restore_info->client.addr; - in_addr_server = (struct sockaddr_in *)&restore_info->server.addr; - } - else - { - in_addr_client = (struct sockaddr_in *)&restore_info->server.addr; - in_addr_server = (struct sockaddr_in *)&restore_info->client.addr; - } - - in_addr_client->sin_family = AF_INET; - in_addr_client->sin_addr = tuple4->addr_v4.src_addr; - in_addr_client->sin_port = tuple4->src_port; - - in_addr_server->sin_family = AF_INET; - in_addr_server->sin_addr = tuple4->addr_v4.dst_addr; - in_addr_server->sin_port = tuple4->dst_port; - } - - if (tuple4->addr_type == ADDR_TUPLE4_TYPE_V6) - { - struct sockaddr_in6 *in6_addr_client; - struct sockaddr_in6 *in6_addr_server; - - if (restore_info->cur_dir == PKT_DIR_NOT_SET || restore_info->cur_dir == PKT_DIR_C2S) - { - in6_addr_client = (struct sockaddr_in6 *)&restore_info->client.addr; - in6_addr_server = (struct sockaddr_in6 *)&restore_info->server.addr; - } - else - { - in6_addr_client = (struct sockaddr_in6 *)&restore_info->server.addr; - in6_addr_server = (struct sockaddr_in6 *)&restore_info->client.addr; - } - - in6_addr_client->sin6_family = AF_INET6; - in6_addr_client->sin6_addr = tuple4->addr_v6.src_addr; - in6_addr_client->sin6_port = tuple4->src_port; - - in6_addr_server->sin6_family = AF_INET6; - in6_addr_server->sin6_addr = tuple4->addr_v6.dst_addr; - in6_addr_server->sin6_port = tuple4->dst_port; - } - - return 0; -} - -// return 0 : success -// return -1 : error -static int handle_control_packet(struct packet_io *handle, marsio_buff_t *rx_buff, int thread_seq, void *ctx) -{ - struct acceptor_thread_ctx *thread = (struct acceptor_thread_ctx *)ctx; - struct acceptor_ctx *acceptor_ctx = thread->ref_acceptor_ctx; - struct global_metrics *g_metrics = thread->ref_metrics; - - struct metadata meta; - if (packet_io_get_metadata(rx_buff, &meta) == -1) - { - TFE_LOG_ERROR(g_default_logger, "%s: unexpected control packet, unable to get metadata", LOG_TAG_PKTIO); - packet_io_dump_metadata(rx_buff, &meta); - return -1; - } - packet_io_dump_metadata(rx_buff, &meta); - - struct ctrl_pkt_parser ctrl_parser; - ctrl_packet_parser_init(&ctrl_parser); - if (ctrl_packet_parser_parse(&ctrl_parser, meta.raw_data + meta.l7_offset, meta.raw_len - meta.l7_offset) == -1) - { - // TFE_LOG_ERROR(g_default_logger, "%s: unexpected control packet, unable to parse data", LOG_TAG_PKTIO); - return -1; - } - ctrl_packet_parser_dump(&ctrl_parser); - - if (ctrl_parser.session_id != meta.session_id) - { - TFE_LOG_ERROR(g_default_logger, "%s: unexpected control packet, metadata's session %lu != control packet's session %lu", LOG_TAG_PKTIO, meta.session_id, ctrl_parser.session_id); - return -1; - } - - switch (ctrl_parser.state) - { - case SESSION_STATE_OPENING: - __atomic_fetch_add(&g_metrics->ctrl_pkt_opening_num, 1, __ATOMIC_RELAXED); - // when session opening, firewall not send policy id - // return handle_session_opening(&meta, &ctrl_parser, thread_seq, ctx); - break; - case SESSION_STATE_CLOSING: - __atomic_fetch_add(&g_metrics->ctrl_pkt_closing_num, 1, __ATOMIC_RELAXED); - return handle_session_closing(&meta, &ctrl_parser, thread_seq, ctx); - case SESSION_STATE_ACTIVE: - __atomic_fetch_add(&g_metrics->ctrl_pkt_active_num, 1, __ATOMIC_RELAXED); - return handle_session_active(&meta, &ctrl_parser, thread_seq, ctx); - case SESSION_STATE_RESETALL: - __atomic_fetch_add(&g_metrics->ctrl_pkt_resetall_num, 1, __ATOMIC_RELAXED); - return handle_session_resetall(&meta, &ctrl_parser, thread_seq, ctx); - default: - __atomic_fetch_add(&g_metrics->ctrl_pkt_error_num, 1, __ATOMIC_RELAXED); - break; - } - - return 0; -} - -static int handle_raw_packet_from_nf(struct packet_io *handle, marsio_buff_t *rx_buff, int thread_seq, void *ctx, int *action_bytes) -{ - int nsend = 0; - struct acceptor_thread_ctx *thread = (struct acceptor_thread_ctx *)ctx; - struct acceptor_ctx *acceptor_ctx = thread->ref_acceptor_ctx; - struct addr_tuple4 inner_addr; - memset(&inner_addr, 0, sizeof(struct addr_tuple4)); - - int raw_len = marsio_buff_datalen(rx_buff); - char *raw_data = marsio_buff_mtod(rx_buff); - *action_bytes = 0; - - struct metadata meta; - if (packet_io_get_metadata(rx_buff, &meta) == -1) - { - TFE_LOG_ERROR(g_default_logger, "%s: unexpected raw packet, unable to get metadata, bypass !!!", LOG_TAG_PKTIO); - packet_io_dump_metadata(rx_buff, &meta); - marsio_send_burst(handle->dev_nf_interface.mr_path, thread_seq, &rx_buff, 1); - *action_bytes = raw_len; - return RAW_PKT_ERR_BYPASS; - } - - struct session_node *node = session_table_search_by_id(thread->session_table, meta.session_id); - if (node == NULL) - { - TFE_LOG_ERROR(g_default_logger, "%s: unexpected raw packet, unable to find session %lu from session table, bypass !!!", LOG_TAG_PKTIO, meta.session_id); - marsio_send_burst(handle->dev_nf_interface.mr_path, thread_seq, &rx_buff, 1); - *action_bytes = raw_len; - return RAW_PKT_ERR_BYPASS; - } - - struct session_ctx *s_ctx = (struct session_ctx *)node->val_data; - - // update sids - if (meta.dir_is_e2i) - { - sids_write_once(&(s_ctx->raw_pkt_e2i_sids), &(meta.sids)); - if (route_ctx_is_empty(&s_ctx->raw_pkt_e2i_route_ctx)) - { - route_ctx_copy(&s_ctx->raw_pkt_e2i_route_ctx, &meta.route_ctx); - } - } - else - { - sids_write_once(&(s_ctx->raw_pkt_i2e_sids), &(meta.sids)); - if (route_ctx_is_empty(&s_ctx->raw_pkt_i2e_route_ctx)) - { - route_ctx_copy(&s_ctx->raw_pkt_i2e_route_ctx, &meta.route_ctx); - } - } - - struct raw_pkt_parser raw_parser; - raw_packet_parser_init(&raw_parser, meta.session_id, LAYER_TYPE_ALL, 8); - const void *payload = raw_packet_parser_parse(&raw_parser, (const void *)meta.raw_data, meta.raw_len); - - if (meta.user_field && TRAFFIC_IS_DECRYPTED) - { - // c2s - if (memcmp(&inner_addr, &s_ctx->first_ctrl_pkt.tuple4, sizeof(struct addr_tuple4)) == 0) { - add_ether_header(raw_data, acceptor_ctx->config->src_mac, acceptor_ctx->config->tap_s_mac); - if (acceptor_ctx->config->enable_iouring) { - io_uring_submit_write_entry(thread->tap_ctx->io_uring_s, raw_data, raw_len); - } - else { - tfe_tap_write_per_thread(thread->tap_ctx->tap_s, raw_data, raw_len, g_default_logger); - } - } - // s2c - else { - add_ether_header(raw_data, acceptor_ctx->config->src_mac, acceptor_ctx->config->tap_c_mac); - if (acceptor_ctx->config->enable_iouring) { - io_uring_submit_write_entry(thread->tap_ctx->io_uring_c, raw_data, raw_len); - } - else { - tfe_tap_write_per_thread(thread->tap_ctx->tap_c, raw_data, raw_len, g_default_logger); - } - } - } - else - { - add_ether_header(raw_data, acceptor_ctx->config->src_mac, acceptor_ctx->config->tap_mac); - // tap0 - if (acceptor_ctx->config->enable_iouring) { - io_uring_submit_write_entry(thread->tap_ctx->io_uring_fd, raw_data, raw_len); - } - else { - tfe_tap_write_per_thread(thread->tap_ctx->tap_fd, raw_data, raw_len, g_default_logger); - } - } -} - struct tcp_option_mss { uint8_t kind; uint8_t length; @@ -959,6 +133,59 @@ struct tcp_option_time_stamp { uint32_t tsecr; } __attribute__((__packed__)); +extern int tcp_policy_enforce(struct tcp_policy_enforcer *tcp_enforcer, struct tfe_cmsg *cmsg); +extern int tfe_proxy_fds_accept(struct tfe_proxy * ctx, int fd_downstream, int fd_upstream, int fd_fake_c, int fd_fake_s, struct tfe_cmsg * cmsg); +extern void chaining_policy_enforce(struct chaining_policy_enforcer *enforcer, struct tfe_cmsg *cmsg, uint64_t rule_id); + +/****************************************************************************** + * STATIC + ******************************************************************************/ + +static void time_echo(struct addr_tuple4 inner_addr) +{ + time_t t; + time(&t); + + char *addr_string = addr_tuple4_to_str(&inner_addr); + TFE_LOG_ERROR(g_default_logger, "%s: session:%s, time:%s", LOG_TAG_PKTIO, addr_string, ctime(&t)); + free(addr_string); +} + +// return 0 : not keepalive packet +// return 1 : is keepalive packet +static int is_downstream_keepalive_packet(marsio_buff_t *rx_buff) +{ + int raw_len = marsio_buff_datalen(rx_buff); + char *raw_data = marsio_buff_mtod(rx_buff); + if (raw_data == NULL || raw_len < (int)(sizeof(struct ethhdr))) + { + return 0; + } + + struct ethhdr *eth_hdr = (struct ethhdr *)raw_data; + if (eth_hdr->h_proto == 0xAAAA) + { + return 1; + } + else + { + return 0; + } +} + +static void session_value_free_cb(void *ctx) +{ + struct session_ctx *s_ctx = (struct session_ctx *)ctx; + session_ctx_free(s_ctx); +} + +static int add_ether_header(void *raw_data, char *src_mac, char *dst_mac){ + struct ethhdr *ether_hdr = (struct ethhdr*)raw_data; + memcpy(ether_hdr->h_dest, dst_mac, sizeof(ether_hdr->h_dest)); + memcpy(ether_hdr->h_source, src_mac, sizeof(ether_hdr->h_source)); + return 0; +} + static int fake_tcp_handshake(struct tfe_proxy *proxy, struct tcp_restore_info *restore_info) { char buffer[1500] = {0}; @@ -1231,168 +458,370 @@ static int overwrite_tcp_mss(struct tfe_cmsg *cmsg, struct tcp_restore_info *res return 0; } -// return 0 : success -// return -1 : error -static int handle_session_opening(struct metadata *meta, struct ctrl_pkt_parser *parser, int thread_seq, void *ctx) +static struct metadata *metadata_new() +{ + struct metadata *meta = (struct metadata *)calloc(1, sizeof(struct metadata)); + + return meta; +} + +static int metadata_is_empty(struct metadata *meta) +{ + return meta->write_ref == 0 ? 1 : 0; +} + +static void metadata_deep_copy(struct metadata *dst, struct metadata *src) +{ + dst->write_ref++; + dst->session_id = src->session_id; + dst->raw_data = (char *)calloc(src->raw_len, sizeof(char)); + memcpy(dst->raw_data, src->raw_data, src->raw_len); + dst->raw_len = src->raw_len; + dst->l7offset = src->l7offset; + dst->is_e2i_dir = src->is_e2i_dir; + dst->is_ctrl_pkt = src->is_ctrl_pkt; + dst->is_decrypted = src->is_decrypted; +} + +static int tcp_restore_set_from_cmsg(struct tfe_cmsg *cmsg, struct tcp_restore_info *restore_info) { - uint8_t *iptmp = NULL; int ret = 0; - int fd_downstream = 0; - int fd_upstream = 0; - int fd_fake_c = 0; - int fd_fake_s = 0; - uint64_t rule_id = 0; - uint16_t size = 0; + uint16_t length = 0; - uint8_t stream_protocol_in_char = 0; - uint8_t enalbe_decrypted_traffic_steering = 0; - struct session_ctx *s_ctx = NULL; - struct addr_tuple4 tuple4; - struct tcp_restore_info restore_info; - memset(&tuple4, 0, sizeof(tuple4)); - memset(&restore_info, 0, sizeof(restore_info)); - - struct sockaddr_in *in_addr_client = (struct sockaddr_in *)&restore_info.client.addr; - struct sockaddr_in *in_addr_server = (struct sockaddr_in *)&restore_info.server.addr; - - struct acceptor_thread_ctx *thread = (struct acceptor_thread_ctx *)ctx; - struct acceptor_ctx *acceptor_ctx = thread->ref_acceptor_ctx; - - struct raw_pkt_parser raw_parser; - raw_packet_parser_init(&raw_parser, meta->session_id, LAYER_TYPE_ALL, 8); - const void *payload = raw_packet_parser_parse(&raw_parser, (const void *)meta->raw_data, meta->raw_len); - if ((char *)payload - meta->raw_data != meta->l7_offset) + uint32_t seq; + ret = tfe_cmsg_get_value(cmsg, TFE_CMSG_TCP_RESTORE_SEQ, (unsigned char *)&seq, sizeof(uint32_t), &length); + if (ret == 0) { - uint16_t offset = (char *)payload - meta->raw_data; - TFE_LOG_ERROR(g_default_logger, "%s: incorrect dataoffset in the control zone of session %lu, offset:%u, l7_offset:%u, payload:%p, raw_data:%p", LOG_TAG_PKTIO, meta->session_id, offset, meta->l7_offset, payload, meta->raw_data); + restore_info->client.seq = ntohl(seq); + restore_info->server.ack = ntohl(seq); } - raw_packet_parser_get_most_inner_tuple4(&raw_parser, &tuple4); - - ret = tfe_cmsg_get_value(parser->cmsg, TFE_CMSG_POLICY_ID, (unsigned char *)&rule_id, sizeof(rule_id), &size); - if (ret < 0) - { - TFE_LOG_ERROR(g_default_logger, "failed at fetch rule_id from cmsg: %s", strerror(-ret)); - goto end; - } - - intercept_policy_enforce(thread->ref_proxy->int_ply_enforcer, parser->cmsg); - tcp_policy_enforce(thread->ref_proxy->tcp_ply_enforcer, parser->cmsg); - for (int i = 0; i < parser->sce_policy_id_num; i++) { - chaining_policy_enforce(thread->ref_proxy->chain_ply_enforcer, parser->cmsg, parser->sce_policy_ids[i]); + uint32_t ack; + ret = tfe_cmsg_get_value(cmsg, TFE_CMSG_TCP_RESTORE_ACK, (unsigned char *)&ack, sizeof(uint32_t), &length); + if (ret == 0) + { + restore_info->client.ack = ntohl(ack); + restore_info->server.seq = ntohl(ack); } - tcp_restore_set_from_cmsg(parser->cmsg, &restore_info); - tcp_restore_set_from_pkg(&tuple4, &restore_info); + uint8_t ts_client; + ret = tfe_cmsg_get_value(cmsg, TFE_CMSG_TCP_RESTORE_TS_CLIENT, (unsigned char *)&ts_client, sizeof(uint8_t), &length); + if (ret == 0) + { + restore_info->client.timestamp_perm = !!ts_client; + } - if (overwrite_tcp_mss(parser->cmsg, &restore_info)) - { - goto end; - } + uint8_t ts_server; + ret = tfe_cmsg_get_value(cmsg, TFE_CMSG_TCP_RESTORE_TS_SERVER, (unsigned char *)&ts_server, sizeof(uint8_t), &length); + if (ret == 0) + { + restore_info->server.timestamp_perm = !!ts_server; + } - iptmp = (uint8_t *)&in_addr_client->sin_addr.s_addr; - // tcp repair C2S - TFE_LOG_DEBUG(g_default_logger, "restore_info: client"); + uint32_t ts_client_val; + ret = tfe_cmsg_get_value(cmsg, TFE_CMSG_TCP_RESTORE_TS_CLIENT_VAL, (unsigned char *)&ts_client_val, sizeof(uint32_t), &length); + if (ret == 0) + { + restore_info->client.ts_val = ntohl(ts_client_val); + } - TFE_LOG_DEBUG(g_default_logger, "\t addr:%d.%d.%d.%d", iptmp[0], iptmp[1], iptmp[2], iptmp[3]); - TFE_LOG_DEBUG(g_default_logger, "\t port:%u", in_addr_client->sin_port); - TFE_LOG_DEBUG(g_default_logger, "\t seq:%u", restore_info.client.seq); - TFE_LOG_DEBUG(g_default_logger, "\t ack:%u", restore_info.client.ack); - TFE_LOG_DEBUG(g_default_logger, "\t ts_val:%u", restore_info.client.ts_val); - TFE_LOG_DEBUG(g_default_logger, "\t mss:%u", restore_info.client.mss); - TFE_LOG_DEBUG(g_default_logger, "\t window:%u", restore_info.client.window); - TFE_LOG_DEBUG(g_default_logger, "\t wscale:%u", restore_info.client.wscale); - TFE_LOG_DEBUG(g_default_logger, "\t wscale_perm:%s", restore_info.client.wscale_perm > 0?"true":"false"); - TFE_LOG_DEBUG(g_default_logger, "\t timestamp_perm:%s", restore_info.client.timestamp_perm > 0?"true":"false"); - TFE_LOG_DEBUG(g_default_logger, "\t sack_perm:%s", restore_info.client.sack_perm > 0?"true":"false"); + uint32_t ts_server_val; + ret = tfe_cmsg_get_value(cmsg, TFE_CMSG_TCP_RESTORE_TS_SERVER_VAL, (unsigned char *)&ts_server_val, sizeof(uint32_t), &length); + if (ret == 0) + { + restore_info->server.ts_val = ntohl(ts_server_val); + } + uint8_t wsacle_client; + ret = tfe_cmsg_get_value(cmsg, TFE_CMSG_TCP_RESTORE_WSACLE_CLIENT, (unsigned char *)&wsacle_client, sizeof(uint8_t), &length); + if (ret == 0) + { + restore_info->client.wscale_perm = true; + restore_info->client.wscale = wsacle_client; + } - iptmp = (uint8_t *)&in_addr_server->sin_addr.s_addr; - // tcp repair C2S - TFE_LOG_DEBUG(g_default_logger, "restore_info: server"); + uint8_t wsacle_server; + ret = tfe_cmsg_get_value(cmsg, TFE_CMSG_TCP_RESTORE_WSACLE_SERVER, (unsigned char *)&wsacle_server, sizeof(uint8_t), &length); + if (ret == 0) + { + restore_info->client.wscale_perm = true; + restore_info->client.wscale = wsacle_server; + } - TFE_LOG_DEBUG(g_default_logger, "\t addr:%d.%d.%d.%d", iptmp[0], iptmp[1], iptmp[2], iptmp[3]); - TFE_LOG_DEBUG(g_default_logger, "\t port:%u", in_addr_server->sin_port); - TFE_LOG_DEBUG(g_default_logger, "\t seq:%u", restore_info.server.seq); - TFE_LOG_DEBUG(g_default_logger, "\t ack:%u", restore_info.server.ack); - TFE_LOG_DEBUG(g_default_logger, "\t ts_val:%u", restore_info.server.ts_val); - TFE_LOG_DEBUG(g_default_logger, "\t mss:%u", restore_info.server.mss); - TFE_LOG_DEBUG(g_default_logger, "\t window:%u", restore_info.server.window); - TFE_LOG_DEBUG(g_default_logger, "\t wscale:%u", restore_info.server.wscale); - TFE_LOG_DEBUG(g_default_logger, "\t wscale_perm:%s", restore_info.server.wscale_perm > 0?"true":"false"); - TFE_LOG_DEBUG(g_default_logger, "\t timestamp_perm:%s", restore_info.server.timestamp_perm > 0?"true":"false"); - TFE_LOG_DEBUG(g_default_logger, "\t sack_perm:%s", restore_info.server.sack_perm > 0?"true":"false"); + uint8_t sack_client; + ret = tfe_cmsg_get_value(cmsg, TFE_CMSG_TCP_RESTORE_SACK_CLIENT, (unsigned char *)&sack_client, sizeof(uint8_t), &length); + if (ret == 0) + { + restore_info->client.sack_perm = !!sack_client; + } + uint8_t sack_server; + ret = tfe_cmsg_get_value(cmsg, TFE_CMSG_TCP_RESTORE_SACK_SERVER, (unsigned char *)&sack_server, sizeof(uint8_t), &length); + if (ret == 0) + { + restore_info->server.sack_perm = !!sack_server; + } - fd_upstream = tfe_tcp_restore_fd_create(&(restore_info.client), &(restore_info.server), thread->ref_tap_config->tap_device, 0x65); - if (fd_upstream < 0) - { - TFE_LOG_ERROR(g_default_logger, "Failed at tcp_restore_fd_create(UPSTREAM)"); - goto end; - } + uint16_t mss_client; + ret = tfe_cmsg_get_value(cmsg, TFE_CMSG_TCP_RESTORE_MSS_CLIENT, (unsigned char *)&mss_client, sizeof(uint16_t), &length); + if (ret == 0) + { + restore_info->client.mss = mss_client; + } - // tcp repair S2C - fd_downstream = tfe_tcp_restore_fd_create(&(restore_info.server), &(restore_info.client), thread->ref_tap_config->tap_device, 0x65); - if (fd_downstream < 0) - { - TFE_LOG_ERROR(g_default_logger, "Failed at tcp_restore_fd_create(DOWNSTREAM)"); - goto end; - } + uint16_t mss_server; + ret = tfe_cmsg_get_value(cmsg, TFE_CMSG_TCP_RESTORE_MSS_SERVER, (unsigned char *)&mss_server, sizeof(uint16_t), &length); + if (ret == 0) + { + restore_info->server.mss = mss_server; + } - tfe_cmsg_get_value(parser->cmsg, TFE_CMSG_TCP_RESTORE_PROTOCOL, (unsigned char *)&stream_protocol_in_char, sizeof(stream_protocol_in_char), &size); - tfe_cmsg_get_value(parser->cmsg, TFE_CMSG_TCP_DECRYPTED_TRAFFIC_STEERING, (unsigned char *)&enalbe_decrypted_traffic_steering, sizeof(enalbe_decrypted_traffic_steering), &size); - if ((STREAM_PROTO_PLAIN == (enum tfe_stream_proto)stream_protocol_in_char && thread->ref_proxy->traffic_steering_options.enable_steering_http) || - (STREAM_PROTO_SSL == (enum tfe_stream_proto)stream_protocol_in_char && thread->ref_proxy->traffic_steering_options.enable_steering_ssl) || - enalbe_decrypted_traffic_steering == 1) - { - if (fake_tcp_handshake(thread->ref_proxy, &restore_info) == -1) - { - TFE_LOG_ERROR(g_default_logger, "Failed at fake_tcp_handshake()"); - goto end; - } + uint16_t window_client; + ret = tfe_cmsg_get_value(cmsg, TFE_CMSG_TCP_RESTORE_WINDOW_CLIENT, (unsigned char *)&window_client, sizeof(uint16_t), &length); + if (ret == 0) + { + restore_info->client.window = window_client; + } - fd_fake_c = tfe_tcp_restore_fd_create(&(restore_info.client), &(restore_info.server), thread->ref_proxy->traffic_steering_options.device_client, thread->ref_proxy->traffic_steering_options.so_mask_client); - if (fd_fake_c < 0) - { - TFE_LOG_ERROR(g_default_logger, "Failed at tcp_restore_fd_create(fd_fake_c)"); - goto end; - } + uint16_t window_server; + ret = tfe_cmsg_get_value(cmsg, TFE_CMSG_TCP_RESTORE_WINDOW_SERVER, (unsigned char *)&window_server, sizeof(uint16_t), &length); + if (ret == 0) + { + restore_info->server.window = window_server; + } - fd_fake_s = tfe_tcp_restore_fd_create(&(restore_info.server), &(restore_info.client), thread->ref_proxy->traffic_steering_options.device_server, thread->ref_proxy->traffic_steering_options.so_mask_server); - if (fd_fake_s < 0) - { - TFE_LOG_ERROR(g_default_logger, "Failed at tcp_restore_fd_create(fd_fake_s)"); - goto end; - } - } - - if (tfe_proxy_fds_accept(thread->ref_proxy, fd_downstream, fd_upstream, fd_fake_c, fd_fake_s, parser->cmsg) < 0) - { - TFE_LOG_ERROR(g_default_logger, "Failed at tfe_proxy_fds_accept()"); - goto end; - } - - s_ctx = session_ctx_new(); - s_ctx->ref_thread_ctx = thread; - s_ctx->session_id = meta->session_id; - s_ctx->cmsg = parser->cmsg; - s_ctx->first_ctrl_pkt.dir_is_e2i = meta->dir_is_e2i; - raw_packet_parser_get_most_inner_tuple4(&raw_parser, &(s_ctx->first_ctrl_pkt.tuple4)); - s_ctx->first_ctrl_pkt.addr_string = addr_tuple4_to_str(&(s_ctx->first_ctrl_pkt.tuple4)); - s_ctx->first_ctrl_pkt.header_data = (char *)calloc(1, meta->l7_offset); - memcpy(s_ctx->first_ctrl_pkt.header_data, meta->raw_data, meta->l7_offset); - s_ctx->first_ctrl_pkt.header_len = meta->l7_offset; - sids_copy(&s_ctx->first_ctrl_pkt.sids, &meta->sids); - route_ctx_copy(&s_ctx->first_ctrl_pkt.route_ctx, &meta->route_ctx); - - TFE_LOG_INFO(g_default_logger, "%s: session %lu %s active first", LOG_TAG_PKTIO, s_ctx->session_id, s_ctx->first_ctrl_pkt.addr_string); - s_ctx->policy_ids = parser->tfe_policy_ids[0]; - - session_table_insert(thread->session_table, s_ctx->session_id, &(s_ctx->first_ctrl_pkt.tuple4), s_ctx, session_value_free_cb); + uint8_t packet_cur_dir; + ret = tfe_cmsg_get_value(cmsg, TFE_CMSG_TCP_RESTORE_INFO_PACKET_CUR_DIR, (unsigned char *)&packet_cur_dir, sizeof(uint8_t), &length); + if (ret == 0) + { + restore_info->cur_dir = (enum tcp_restore_pkt_dir)packet_cur_dir; + } return 0; -end: - return -1; +} + +static int tcp_restore_set_from_pkg(struct addr_tuple4 *tuple4, struct tcp_restore_info *restore_info) +{ + if (tuple4->addr_type == ADDR_TUPLE4_TYPE_V4) + { + struct sockaddr_in *in_addr_client; + struct sockaddr_in *in_addr_server; + + if (restore_info->cur_dir == PKT_DIR_NOT_SET || restore_info->cur_dir == PKT_DIR_C2S) + { + in_addr_client = (struct sockaddr_in *)&restore_info->client.addr; + in_addr_server = (struct sockaddr_in *)&restore_info->server.addr; + } + else + { + in_addr_client = (struct sockaddr_in *)&restore_info->server.addr; + in_addr_server = (struct sockaddr_in *)&restore_info->client.addr; + } + + in_addr_client->sin_family = AF_INET; + in_addr_client->sin_addr = tuple4->addr_v4.src_addr; + in_addr_client->sin_port = tuple4->src_port; + + in_addr_server->sin_family = AF_INET; + in_addr_server->sin_addr = tuple4->addr_v4.dst_addr; + in_addr_server->sin_port = tuple4->dst_port; + } + + if (tuple4->addr_type == ADDR_TUPLE4_TYPE_V6) + { + struct sockaddr_in6 *in6_addr_client; + struct sockaddr_in6 *in6_addr_server; + + if (restore_info->cur_dir == PKT_DIR_NOT_SET || restore_info->cur_dir == PKT_DIR_C2S) + { + in6_addr_client = (struct sockaddr_in6 *)&restore_info->client.addr; + in6_addr_server = (struct sockaddr_in6 *)&restore_info->server.addr; + } + else + { + in6_addr_client = (struct sockaddr_in6 *)&restore_info->server.addr; + in6_addr_server = (struct sockaddr_in6 *)&restore_info->client.addr; + } + + in6_addr_client->sin6_family = AF_INET6; + in6_addr_client->sin6_addr = tuple4->addr_v6.src_addr; + in6_addr_client->sin6_port = tuple4->src_port; + + in6_addr_server->sin6_family = AF_INET6; + in6_addr_server->sin6_addr = tuple4->addr_v6.dst_addr; + in6_addr_server->sin6_port = tuple4->dst_port; + } + + return 0; +} + +// return 0 : success +// return -1 : error +static int packet_io_config(const char *profile, struct config *config) +{ + MESA_load_profile_int_def(profile, "PACKET_IO", "rx_burst_max", (int *)&(config->rx_burst_max), 1); + MESA_load_profile_string_nodef(profile, "PACKET_IO", "app_symbol", config->app_symbol, sizeof(config->app_symbol)); + MESA_load_profile_string_nodef(profile, "PACKET_IO", "dev_nf_interface", config->dev_nf_interface, sizeof(config->dev_nf_interface)); + + if (config->rx_burst_max > RX_BURST_MAX) + { + TFE_LOG_ERROR(g_default_logger, "%s: invalid rx_burst_max, exceeds limit %d", LOG_TAG_PKTIO, RX_BURST_MAX); + return -1; + } + + if (strlen(config->app_symbol) == 0) + { + TFE_LOG_ERROR(g_default_logger, "%s: invalid app_symbol in %s", LOG_TAG_PKTIO, profile); + return -1; + } + + if (strlen(config->dev_nf_interface) == 0) + { + TFE_LOG_ERROR(g_default_logger, "%s: invalid dev_nf_interface in %s", LOG_TAG_PKTIO, profile); + return -1; + } + + TFE_LOG_DEBUG(g_default_logger, "%s: PACKET_IO->bypass_all_traffic : %d", LOG_TAG_PKTIO, config->bypass_all_traffic); + TFE_LOG_DEBUG(g_default_logger, "%s: PACKET_IO->rx_burst_max : %d", LOG_TAG_PKTIO, config->rx_burst_max); + TFE_LOG_DEBUG(g_default_logger, "%s: PACKET_IO->app_symbol : %s", LOG_TAG_PKTIO, config->app_symbol); + TFE_LOG_DEBUG(g_default_logger, "%s: PACKET_IO->dev_nf_interface : %s", LOG_TAG_PKTIO, config->dev_nf_interface); + + return 0; +} + +// return 0 : success +// return -1 : error +static int packet_io_get_metadata(marsio_buff_t *rx_buff, struct metadata *meta) +{ + memset(meta, 0, sizeof(struct metadata)); + + if (marsio_buff_get_metadata(rx_buff, MR_BUFF_SESSION_ID, &(meta->session_id), sizeof(meta->session_id)) <= 0) + { + TFE_LOG_ERROR(g_default_logger, "%s: unable to get session_id from metadata", LOG_TAG_PKTIO); + return -1; + } + + meta->raw_len = marsio_buff_datalen(rx_buff); + meta->raw_data = marsio_buff_mtod(rx_buff); + if (meta->raw_data == NULL || meta->raw_len == 0) + { + TFE_LOG_ERROR(g_default_logger, "%s: unable to get raw_data from metadata", LOG_TAG_PKTIO); + return -1; + } + + // 1: E2I + // 0: I2E + if (marsio_buff_get_metadata(rx_buff, MR_BUFF_DIR, &(meta->is_e2i_dir), sizeof(meta->is_e2i_dir)) <= 0) + { + TFE_LOG_ERROR(g_default_logger, "%s: unable to get buff_dir from metadata", LOG_TAG_PKTIO); + return -1; + } + + if (marsio_buff_is_ctrlbuf(rx_buff)) + { + meta->is_ctrl_pkt = 1; + // only control packet set MR_BUFF_PAYLOAD_OFFSET + if (marsio_buff_get_metadata(rx_buff, MR_BUFF_PAYLOAD_OFFSET, &(meta->l7offset), sizeof(meta->l7offset)) <= 0) + { + TFE_LOG_ERROR(g_default_logger, "%s: unable to get l7offset from metadata", LOG_TAG_PKTIO); + return -1; + } + } + else + { + meta->is_ctrl_pkt = 0; + uint16_t user_data = 0; + if (marsio_buff_get_metadata(rx_buff, MR_BUFF_USER_0, &(user_data), sizeof(user_data)) <= 0) + { + TFE_LOG_ERROR(g_default_logger, "%s: unable to get is_decrypted from metadata", LOG_TAG_PKTIO); + return -1; + } + if (user_data & TRAFFIC_IS_DECRYPTED) + { + meta->is_decrypted = 1; + } + else + { + meta->is_decrypted = 0; + } + } + + meta->route_ctx.len = marsio_buff_get_metadata(rx_buff, MR_BUFF_ROUTE_CTX, meta->route_ctx.data, sizeof(meta->route_ctx.data)); + if (meta->route_ctx.len <= 0) + { + TFE_LOG_ERROR(g_default_logger, "%s: unable to get route_ctx from metadata", LOG_TAG_PKTIO); + return -1; + } + + meta->sids.num = marsio_buff_get_sid_list(rx_buff, meta->sids.elems, sizeof(meta->sids.elems) / sizeof(meta->sids.elems[0])); + if (meta->sids.num < 0) + { + TFE_LOG_ERROR(g_default_logger, "%s: unable to get sid_list from metadata", LOG_TAG_PKTIO); + return -1; + } + + return 0; +} + +// return 0 : success +// return -1 : error +static int packet_io_set_metadata(marsio_buff_t *tx_buff, struct metadata *meta) +{ + if (meta->session_id) + { + if (marsio_buff_set_metadata(tx_buff, MR_BUFF_SESSION_ID, &(meta->session_id), sizeof(meta->session_id)) != 0) + { + TFE_LOG_ERROR(g_default_logger, "%s: unable to set session_id for metadata", LOG_TAG_PKTIO); + return -1; + } + } + + if (meta->is_ctrl_pkt) + { + if (marsio_buff_set_metadata(tx_buff, MR_BUFF_PAYLOAD_OFFSET, &(meta->l7offset), sizeof(meta->l7offset)) != 0) + { + TFE_LOG_ERROR(g_default_logger, "%s: unable to set l7offset for metadata", LOG_TAG_PKTIO); + return -1; + } + } + else + { + uint16_t user_data = 0; + if (meta->is_decrypted) + { + user_data = SET_TRAFFIC_IS_DECRYPTED(user_data); + } + if (marsio_buff_set_metadata(tx_buff, MR_BUFF_USER_0, &(user_data), sizeof(user_data)) != 0) + { + TFE_LOG_ERROR(g_default_logger, "%s: unable to set is_decrypted for metadata", LOG_TAG_PKTIO); + return -1; + } + } + + if (meta->route_ctx.len > 0) + { + if (marsio_buff_set_metadata(tx_buff, MR_BUFF_ROUTE_CTX, meta->route_ctx.data, meta->route_ctx.len) != 0) + { + TFE_LOG_ERROR(g_default_logger, "%s: unable to set route_ctx for metadata", LOG_TAG_PKTIO); + return -1; + } + } + + if (meta->sids.num > 0) + { + if (marsio_buff_set_sid_list(tx_buff, meta->sids.elems, meta->sids.num) != 0) + { + TFE_LOG_ERROR(g_default_logger, "%s: unable to set sid_list for metadata", LOG_TAG_PKTIO); + return -1; + } + } + + return 0; +} + +static void packet_io_dump_metadata(marsio_buff_t *tx_buff, struct metadata *meta) +{ + TFE_LOG_DEBUG(g_default_logger, "%s: META={session_id: %lu, raw_len: %d, is_e2i_dir: %d, is_ctrl_pkt: %d, l7offset: %d, is_decrypted: %u, sids_num: %d}", LOG_TAG_PKTIO, meta->session_id, meta->raw_len, meta->is_e2i_dir, meta->is_ctrl_pkt, meta->l7offset, meta->is_decrypted, meta->sids.num); } /* @@ -1419,41 +848,210 @@ static void send_event_log(struct session_ctx *s_ctx, int thread_seq, void *ctx) struct acceptor_ctx *acceptor_ctx = thread->ref_acceptor_ctx; struct packet_io *packet_io = thread->ref_io; - char buffer[32] = {0}; - sprintf(buffer, "%lu", s_ctx->session_id); + return; +} - cJSON *root = cJSON_CreateObject(); - cJSON_AddStringToObject(root, "tsync", "1.0"); - cJSON_AddStringToObject(root, "session_id", buffer); - cJSON_AddStringToObject(root, "state", "closing"); - cJSON_AddStringToObject(root, "method", "log_update"); - cJSON *sf_profile_ids = cJSON_CreateArray(); +// return 0 : success +// return -1 : error +static int handle_session_opening(struct metadata *meta, struct ctrl_pkt_parser *parser, int thread_seq, void *ctx) +{ + uint8_t *iptmp = NULL; + int ret = 0; + int fd_downstream = 0; + int fd_upstream = 0; + int fd_fake_c = 0; + int fd_fake_s = 0; + uint64_t rule_id = 0; + uint16_t size = 0; + char *addr_str = NULL; - cJSON *params = cJSON_CreateObject(); - cJSON_AddItemToObject(params, "sf_profile_ids", sf_profile_ids); - cJSON_AddItemToObject(root, "params", params); - char *json_str = cJSON_PrintUnformatted(root); + uint8_t stream_protocol_in_char = 0; + uint8_t enalbe_decrypted_traffic_steering = 0; + struct ethhdr *ether_hdr = NULL; + struct session_ctx *s_ctx = NULL; + struct addr_tuple4 inner_tuple4; + struct tcp_restore_info restore_info; + memset(&inner_tuple4, 0, sizeof(inner_tuple4)); + memset(&restore_info, 0, sizeof(restore_info)); - TFE_LOG_INFO(g_default_logger, "%s: session %lu %s event log: %s", LOG_TAG_METRICS, s_ctx->session_id, s_ctx->first_ctrl_pkt.addr_string, json_str); + struct sockaddr_in *in_addr_client = (struct sockaddr_in *)&restore_info.client.addr; + struct sockaddr_in *in_addr_server = (struct sockaddr_in *)&restore_info.server.addr; - marsio_buff_t *tx_buffs[1]; - marsio_buff_malloc_device(packet_io->dev_nf_interface.mr_dev, tx_buffs, 1, 0, thread_seq); - char *dst = marsio_buff_append(tx_buffs[0], s_ctx->first_ctrl_pkt.header_len + strlen(json_str)); - memcpy(dst, s_ctx->first_ctrl_pkt.header_data, s_ctx->first_ctrl_pkt.header_len); - memcpy(dst + s_ctx->first_ctrl_pkt.header_len, json_str, strlen(json_str)); + struct acceptor_thread_ctx *thread = (struct acceptor_thread_ctx *)ctx; + struct acceptor_ctx *acceptor_ctx = thread->ref_acceptor_ctx; - struct metadata meta = {0}; - meta.session_id = s_ctx->session_id; - meta.is_ctrl_pkt = 1; - meta.l7_offset = s_ctx->first_ctrl_pkt.header_len; - meta.sids.num = 1; - meta.sids.elems[0] = acceptor_ctx->firewall_sids; - route_ctx_copy(&meta.route_ctx, &s_ctx->first_ctrl_pkt.route_ctx); - packet_io_set_metadata(tx_buffs[0], &meta); - marsio_send_burst(packet_io->dev_nf_interface.mr_path, thread_seq, tx_buffs, 1); + struct raw_pkt_parser raw_parser; + raw_packet_parser_init(&raw_parser, meta->session_id, LAYER_TYPE_ALL, 8); + const void *payload = raw_packet_parser_parse(&raw_parser, (const void *)meta->raw_data, meta->raw_len); + if ((char *)payload - meta->raw_data != meta->l7offset) + { + uint16_t offset = (char *)payload - meta->raw_data; + TFE_LOG_ERROR(g_default_logger, "%s: incorrect dataoffset in the control zone of session %lu, offset:%u, l7offset:%u, payload:%p, raw_data:%p", LOG_TAG_PKTIO, meta->session_id, offset, meta->l7offset, payload, meta->raw_data); + } - free(json_str); - cJSON_Delete(root); + raw_packet_parser_get_most_inner_tuple4(&raw_parser, &inner_tuple4); + + intercept_policy_enforce(thread->ref_proxy->int_ply_enforcer, parser->cmsg); + tcp_policy_enforce(thread->ref_proxy->tcp_ply_enforcer, parser->cmsg); + for (int i = 0; i < parser->sce_policy_id_num; i++) { + chaining_policy_enforce(thread->ref_proxy->chain_ply_enforcer, parser->cmsg, parser->sce_policy_ids[i]); + } + + tcp_restore_set_from_cmsg(parser->cmsg, &restore_info); + tcp_restore_set_from_pkg(&inner_tuple4, &restore_info); + + if (overwrite_tcp_mss(parser->cmsg, &restore_info)) + { + goto end; + } + + iptmp = (uint8_t *)&in_addr_client->sin_addr.s_addr; + // tcp repair C2S + addr_str = addr_tuple4_to_str(&inner_tuple4); + TFE_LOG_DEBUG(g_default_logger, "restore_info session %lu %s : client", meta->session_id, addr_str); + + TFE_LOG_DEBUG(g_default_logger, "\t addr:%d.%d.%d.%d", iptmp[0], iptmp[1], iptmp[2], iptmp[3]); + TFE_LOG_DEBUG(g_default_logger, "\t port:%u", in_addr_client->sin_port); + TFE_LOG_DEBUG(g_default_logger, "\t seq:%u", restore_info.client.seq); + TFE_LOG_DEBUG(g_default_logger, "\t ack:%u", restore_info.client.ack); + TFE_LOG_DEBUG(g_default_logger, "\t ts_val:%u", restore_info.client.ts_val); + TFE_LOG_DEBUG(g_default_logger, "\t mss:%u", restore_info.client.mss); + TFE_LOG_DEBUG(g_default_logger, "\t window:%u", restore_info.client.window); + TFE_LOG_DEBUG(g_default_logger, "\t wscale:%u", restore_info.client.wscale); + TFE_LOG_DEBUG(g_default_logger, "\t wscale_perm:%s", restore_info.client.wscale_perm > 0?"true":"false"); + TFE_LOG_DEBUG(g_default_logger, "\t timestamp_perm:%s", restore_info.client.timestamp_perm > 0?"true":"false"); + TFE_LOG_DEBUG(g_default_logger, "\t sack_perm:%s", restore_info.client.sack_perm > 0?"true":"false"); + + + iptmp = (uint8_t *)&in_addr_server->sin_addr.s_addr; + // tcp repair C2S + TFE_LOG_DEBUG(g_default_logger, "restore_info session %lu %s : server", meta->session_id, addr_str); + + TFE_LOG_DEBUG(g_default_logger, "\t addr:%d.%d.%d.%d", iptmp[0], iptmp[1], iptmp[2], iptmp[3]); + TFE_LOG_DEBUG(g_default_logger, "\t port:%u", in_addr_server->sin_port); + TFE_LOG_DEBUG(g_default_logger, "\t seq:%u", restore_info.server.seq); + TFE_LOG_DEBUG(g_default_logger, "\t ack:%u", restore_info.server.ack); + TFE_LOG_DEBUG(g_default_logger, "\t ts_val:%u", restore_info.server.ts_val); + TFE_LOG_DEBUG(g_default_logger, "\t mss:%u", restore_info.server.mss); + TFE_LOG_DEBUG(g_default_logger, "\t window:%u", restore_info.server.window); + TFE_LOG_DEBUG(g_default_logger, "\t wscale:%u", restore_info.server.wscale); + TFE_LOG_DEBUG(g_default_logger, "\t wscale_perm:%s", restore_info.server.wscale_perm > 0?"true":"false"); + TFE_LOG_DEBUG(g_default_logger, "\t timestamp_perm:%s", restore_info.server.timestamp_perm > 0?"true":"false"); + TFE_LOG_DEBUG(g_default_logger, "\t sack_perm:%s", restore_info.server.sack_perm > 0?"true":"false"); + + free(addr_str); + + fd_upstream = tfe_tcp_restore_fd_create(&(restore_info.client), &(restore_info.server), thread->ref_tap_config->tap_device, 0x65); + if (fd_upstream < 0) + { + TFE_LOG_ERROR(g_default_logger, "Failed at tcp_restore_fd_create(UPSTREAM)"); + goto end; + } + + // tcp repair S2C + fd_downstream = tfe_tcp_restore_fd_create(&(restore_info.server), &(restore_info.client), thread->ref_tap_config->tap_device, 0x65); + if (fd_downstream < 0) + { + TFE_LOG_ERROR(g_default_logger, "Failed at tcp_restore_fd_create(DOWNSTREAM)"); + goto end; + } + + tfe_cmsg_get_value(parser->cmsg, TFE_CMSG_TCP_RESTORE_PROTOCOL, (unsigned char *)&stream_protocol_in_char, sizeof(stream_protocol_in_char), &size); + tfe_cmsg_get_value(parser->cmsg, TFE_CMSG_TCP_DECRYPTED_TRAFFIC_STEERING, (unsigned char *)&enalbe_decrypted_traffic_steering, sizeof(enalbe_decrypted_traffic_steering), &size); + if ((STREAM_PROTO_PLAIN == (enum tfe_stream_proto)stream_protocol_in_char && thread->ref_proxy->traffic_steering_options.enable_steering_http) || + (STREAM_PROTO_SSL == (enum tfe_stream_proto)stream_protocol_in_char && thread->ref_proxy->traffic_steering_options.enable_steering_ssl) || + enalbe_decrypted_traffic_steering == 1) + { + if (fake_tcp_handshake(thread->ref_proxy, &restore_info) == -1) + { + TFE_LOG_ERROR(g_default_logger, "session %lu Failed at fake_tcp_handshake()", meta->session_id); + goto end; + } + + fd_fake_c = tfe_tcp_restore_fd_create(&(restore_info.client), &(restore_info.server), thread->ref_proxy->traffic_steering_options.device_client, thread->ref_proxy->traffic_steering_options.so_mask_client); + if (fd_fake_c < 0) + { + TFE_LOG_ERROR(g_default_logger, "session %lu Failed at tcp_restore_fd_create(fd_fake_c)", meta->session_id); + goto end; + } + + fd_fake_s = tfe_tcp_restore_fd_create(&(restore_info.server), &(restore_info.client), thread->ref_proxy->traffic_steering_options.device_server, thread->ref_proxy->traffic_steering_options.so_mask_server); + if (fd_fake_s < 0) + { + TFE_LOG_ERROR(g_default_logger, "session %lu Failed at tcp_restore_fd_create(fd_fake_s)", meta->session_id); + goto end; + } + } + + if (tfe_proxy_fds_accept(thread->ref_proxy, fd_downstream, fd_upstream, fd_fake_c, fd_fake_s, parser->cmsg) < 0) + { + TFE_LOG_ERROR(g_default_logger, "session %lu Failed at tfe_proxy_fds_accept()", meta->session_id); + goto end; + } + + s_ctx = session_ctx_new(); + s_ctx->raw_meta_i2e = metadata_new(); + s_ctx->raw_meta_e2i = metadata_new(); + s_ctx->ctrl_meta = metadata_new(); + + s_ctx->ref_thread_ctx = thread; + s_ctx->session_id = meta->session_id; + s_ctx->session_addr = addr_tuple4_to_str(&inner_tuple4); + s_ctx->cmsg = parser->cmsg; + metadata_deep_copy(s_ctx->ctrl_meta, meta); + + ether_hdr = (struct ethhdr *)(s_ctx->ctrl_meta->raw_data); + memcpy(s_ctx->client_mac, ether_hdr->h_source, 6); + memcpy(s_ctx->server_mac, ether_hdr->h_dest, 6); + + // c2s + s_ctx->c2s_info.is_e2i_dir = meta->is_e2i_dir; + s_ctx->c2s_info.tuple4 = inner_tuple4; + + // s2c + s_ctx->s2c_info.is_e2i_dir = !meta->is_e2i_dir; + addr_tuple4_reverse(&inner_tuple4, &s_ctx->s2c_info.tuple4); + + s_ctx->policy_ids = parser->tfe_policy_ids[0]; + + sids_copy(&s_ctx->ctrl_meta->sids, &meta->sids); + route_ctx_copy(&s_ctx->ctrl_meta->route_ctx, &meta->route_ctx); + if (s_ctx->c2s_info.is_e2i_dir) { + sids_copy(&s_ctx->raw_meta_e2i->sids, &parser->seq_sids); + route_ctx_copy(&s_ctx->raw_meta_e2i->route_ctx, &parser->seq_route_ctx); + sids_copy(&s_ctx->raw_meta_i2e->sids, &parser->ack_sids); + route_ctx_copy(&s_ctx->raw_meta_i2e->route_ctx, &parser->ack_route_ctx); + } + else + { + sids_copy(&s_ctx->raw_meta_i2e->sids, &parser->seq_sids); + route_ctx_copy(&s_ctx->raw_meta_i2e->route_ctx, &parser->seq_route_ctx); + sids_copy(&s_ctx->raw_meta_e2i->sids, &parser->ack_sids); + route_ctx_copy(&s_ctx->raw_meta_e2i->route_ctx, &parser->ack_route_ctx); + } + + TFE_LOG_INFO(g_default_logger, "%s: session %lu %s active first", LOG_TAG_PKTIO, s_ctx->session_id, s_ctx->session_addr); + + session_table_insert(thread->session_table, s_ctx->session_id, &(s_ctx->c2s_info.tuple4), s_ctx, session_value_free_cb); + + return 0; +end: + return -1; +} + +// return 0 : success +// return -1 : error +static int handle_session_active(struct metadata *meta, struct ctrl_pkt_parser *parser, int thread_seq, void *ctx) +{ + struct acceptor_thread_ctx *thread = (struct acceptor_thread_ctx *)ctx; + + struct session_node *node = session_table_search_by_id(thread->session_table, meta->session_id); + if (!node) + { + return handle_session_opening(meta, parser, thread_seq, ctx); + } + + return 0; } // return 0 : success @@ -1466,7 +1064,7 @@ static int handle_session_closing(struct metadata *meta, struct ctrl_pkt_parser if (node) { struct session_ctx *s_ctx = (struct session_ctx *)node->val_data; - TFE_LOG_INFO(g_default_logger, "%s: session %lu %s closing", LOG_TAG_PKTIO, s_ctx->session_id, s_ctx->first_ctrl_pkt.addr_string); + TFE_LOG_INFO(g_default_logger, "%s: session %lu closing", LOG_TAG_PKTIO, s_ctx->session_id); send_event_log(s_ctx, thread_seq, ctx); @@ -1477,35 +1075,6 @@ static int handle_session_closing(struct metadata *meta, struct ctrl_pkt_parser return -1; } -// return 0 : success -// return -1 : error -static int handle_session_active(struct metadata *meta, struct ctrl_pkt_parser *parser, int thread_seq, void *ctx) -{ - struct acceptor_thread_ctx *thread = (struct acceptor_thread_ctx *)ctx; - - struct session_node *node = session_table_search_by_id(thread->session_table, meta->session_id); - if (node) - { - struct raw_pkt_parser raw_parser; - raw_packet_parser_init(&raw_parser, meta->session_id, LAYER_TYPE_ALL, 8); - const void *payload = raw_packet_parser_parse(&raw_parser, (const void *)meta->raw_data, meta->raw_len); - if ((char *)payload - (char *)&meta->raw_data != meta->l7_offset) - { - TFE_LOG_ERROR(g_default_logger, "%s: incorrect dataoffset in the control zone of session %lu", LOG_TAG_PKTIO, meta->session_id); - } - - struct session_ctx *s_ctx = (struct session_ctx *)node->val_data; - TFE_LOG_INFO(g_default_logger, "%s: session %lu %s active again", LOG_TAG_PKTIO, s_ctx->session_id, s_ctx->first_ctrl_pkt.addr_string); - s_ctx->policy_ids = parser->tfe_policy_ids[0]; - } - else - { - return handle_session_opening(meta, parser, thread_seq, ctx); - } - - return 0; -} - // return 0 : success // return -1 : error static int handle_session_resetall(struct metadata *meta, struct ctrl_pkt_parser *parser, int thread_seq, void *ctx) @@ -1524,30 +1093,439 @@ static int handle_session_resetall(struct metadata *meta, struct ctrl_pkt_parser return 0; } -static void session_value_free_cb(void *ctx) +// return 0 : success +// return -1 : error +static int handle_control_packet(struct packet_io *handle, marsio_buff_t *rx_buff, int thread_seq, void *ctx) { - struct session_ctx *s_ctx = (struct session_ctx *)ctx; - session_ctx_free(s_ctx); -} + struct acceptor_thread_ctx *thread = (struct acceptor_thread_ctx *)ctx; + struct acceptor_ctx *acceptor_ctx = thread->ref_acceptor_ctx; + struct global_metrics *g_metrics = thread->ref_metrics; -// return 0 : not keepalive packet -// return 1 : is keepalive packet -static int is_downstream_keepalive_packet(marsio_buff_t *rx_buff) -{ - int raw_len = marsio_buff_datalen(rx_buff); - char *raw_data = marsio_buff_mtod(rx_buff); - if (raw_data == NULL || raw_len < (int)(sizeof(struct ethhdr))) + struct metadata meta; + if (packet_io_get_metadata(rx_buff, &meta) == -1) { - return 0; + TFE_LOG_ERROR(g_default_logger, "%s: unexpected control packet, unable to get metadata", LOG_TAG_PKTIO); + packet_io_dump_metadata(rx_buff, &meta); + return -1; } - struct ethhdr *eth_hdr = (struct ethhdr *)raw_data; - if (eth_hdr->h_proto == 0xAAAA) + struct ctrl_pkt_parser ctrl_parser; + ctrl_packet_parser_init(&ctrl_parser); + if (ctrl_packet_parser_parse(&ctrl_parser, meta.raw_data + meta.l7offset, meta.raw_len - meta.l7offset) == -1) { - return 1; + TFE_LOG_ERROR(g_default_logger, "%s: unexpected control packet, unable to parse data", LOG_TAG_PKTIO); + return -1; + } + ctrl_packet_parser_dump(&ctrl_parser); + + if (ctrl_parser.session_id != meta.session_id) + { + TFE_LOG_ERROR(g_default_logger, "%s: unexpected control packet, metadata's session %lu != control packet's session %lu", LOG_TAG_PKTIO, meta.session_id, ctrl_parser.session_id); + return -1; + } + + switch (ctrl_parser.state) + { + case SESSION_STATE_OPENING: + __atomic_fetch_add(&g_metrics->ctrl_pkt_opening_num, 1, __ATOMIC_RELAXED); + // when session opening, firewall not send policy id + // return handle_session_opening(&meta, &ctrl_parser, thread_seq, ctx); + break; + case SESSION_STATE_CLOSING: + __atomic_fetch_add(&g_metrics->ctrl_pkt_closing_num, 1, __ATOMIC_RELAXED); + return handle_session_closing(&meta, &ctrl_parser, thread_seq, ctx); + case SESSION_STATE_ACTIVE: + __atomic_fetch_add(&g_metrics->ctrl_pkt_active_num, 1, __ATOMIC_RELAXED); + return handle_session_active(&meta, &ctrl_parser, thread_seq, ctx); + case SESSION_STATE_RESETALL: + __atomic_fetch_add(&g_metrics->ctrl_pkt_resetall_num, 1, __ATOMIC_RELAXED); + return handle_session_resetall(&meta, &ctrl_parser, thread_seq, ctx); + default: + __atomic_fetch_add(&g_metrics->ctrl_pkt_error_num, 1, __ATOMIC_RELAXED); + break; + } + + return 0; +} + +static int handle_raw_packet_from_nf(struct packet_io *handle, marsio_buff_t *rx_buff, int thread_seq, void *ctx) +{ + struct acceptor_thread_ctx *thread = (struct acceptor_thread_ctx *)ctx; + struct acceptor_ctx *acceptor_ctx = thread->ref_acceptor_ctx; + struct global_metrics *g_metrics = thread->ref_metrics; + struct addr_tuple4 inner_addr; + memset(&inner_addr, 0, sizeof(struct addr_tuple4)); + + int raw_len = marsio_buff_datalen(rx_buff); + char *raw_data = marsio_buff_mtod(rx_buff); + char *buff = NULL; + int buff_size = 0; + + struct metadata meta; + if (packet_io_get_metadata(rx_buff, &meta) == -1) + { + TFE_LOG_ERROR(g_default_logger, "%s: unexpected raw packet, unable to get metadata, bypass !!!", LOG_TAG_PKTIO); + packet_io_dump_metadata(rx_buff, &meta); + marsio_send_burst(handle->dev_nf_interface.mr_path, thread_seq, &rx_buff, 1); + return -1; + } + + struct session_node *node = session_table_search_by_id(thread->session_table, meta.session_id); + if (node == NULL) + { + TFE_LOG_ERROR(g_default_logger, "%s: unexpected raw packet, unable to find session %lu from session table, bypass !!!", LOG_TAG_PKTIO, meta.session_id); + marsio_send_burst(handle->dev_nf_interface.mr_path, thread_seq, &rx_buff, 1); + return -1; + } + + struct session_ctx *s_ctx = (struct session_ctx *)node->val_data; + + if (meta.is_e2i_dir) + { + if (metadata_is_empty(s_ctx->raw_meta_e2i)) + { + metadata_deep_copy(s_ctx->raw_meta_e2i, &meta); + } } else { - return 0; + if (metadata_is_empty(s_ctx->raw_meta_i2e)) + { + metadata_deep_copy(s_ctx->raw_meta_i2e, &meta); + } + } + + if (meta.is_decrypted) + { + // c2s + if (meta.is_e2i_dir == s_ctx->c2s_info.is_e2i_dir) { + add_ether_header(raw_data, acceptor_ctx->config->src_mac, acceptor_ctx->config->tap_s_mac); + if (acceptor_ctx->config->enable_iouring) { + io_uring_submit_write_entry(thread->tap_ctx->io_uring_s, raw_data, raw_len); + } + else { + tfe_tap_write_per_thread(thread->tap_ctx->tap_s, raw_data, raw_len, g_default_logger); + } + throughput_metrics_inc(&g_metrics->tap_s_pkt_tx, 1, raw_len); + } + // s2c + else { + add_ether_header(raw_data, acceptor_ctx->config->src_mac, acceptor_ctx->config->tap_c_mac); + if (acceptor_ctx->config->enable_iouring) { + io_uring_submit_write_entry(thread->tap_ctx->io_uring_c, raw_data, raw_len); + } + else { + tfe_tap_write_per_thread(thread->tap_ctx->tap_c, raw_data, raw_len, g_default_logger); + } + throughput_metrics_inc(&g_metrics->tap_c_pkt_tx, 1, raw_len); + } + } + else + { +#if 0 + struct raw_pkt_parser raw_parser; + raw_packet_parser_init(&raw_parser, meta->session_id, LAYER_TYPE_ALL, 8); + const void *payload = raw_packet_parser_parse(&raw_parser, (const void *)meta->raw_data, meta->raw_len); + buff_size = raw_len - ((char *)payload - meta->raw_data) + sizeof(struct ethhdr) + sizeof(struct ip) + sizeof(struct tcphdr); +#endif + // send to tap0 + add_ether_header(raw_data, acceptor_ctx->config->src_mac, acceptor_ctx->config->tap_mac); + if (acceptor_ctx->config->enable_iouring) { + io_uring_submit_write_entry(thread->tap_ctx->io_uring_fd, raw_data, raw_len); + } + else { + tfe_tap_write_per_thread(thread->tap_ctx->tap_fd, raw_data, raw_len, g_default_logger); + } + throughput_metrics_inc(&g_metrics->tap_pkt_tx, 1, raw_len); + } + marsio_buff_free(handle->instance, &rx_buff, 1, 0, thread_seq); + return 0; +} + + +/****************************************************************************** + * EXTERN + ******************************************************************************/ + +int packet_io_thread_init(struct packet_io *handle, struct acceptor_thread_ctx *thread_ctx) +{ + if (marsio_thread_init(handle->instance) != 0) + { + TFE_LOG_ERROR(g_default_logger, "%s: unable to init marsio thread %d", LOG_TAG_PKTIO, thread_ctx->thread_index); + return -1; + } + + return 0; +} + +void packet_io_thread_wait(struct packet_io *handle, struct acceptor_thread_ctx *thread_ctx, int timeout_ms) +{ + struct mr_vdev *vdevs[] = {handle->dev_nf_interface.mr_dev}; + + marsio_poll_wait(handle->instance, vdevs, 1, thread_ctx->thread_index, timeout_ms); +} + +void packet_io_destory(struct packet_io *handle) +{ + if (handle) + { + if (handle->dev_nf_interface.mr_path) + { + marsio_sendpath_destory(handle->dev_nf_interface.mr_path); + handle->dev_nf_interface.mr_path = NULL; + } + + if (handle->dev_nf_interface.mr_dev) + { + marsio_close_device(handle->dev_nf_interface.mr_dev); + handle->dev_nf_interface.mr_dev = NULL; + } + + if (handle->instance) + { + marsio_destory(handle->instance); + handle->instance = NULL; + } + + free(handle); + handle = NULL; } } + +struct packet_io *packet_io_create(const char *profile, int thread_num, cpu_set_t *coremask) +{ + int opt = 1; + struct packet_io *handle = (struct packet_io *)calloc(1, sizeof(struct packet_io)); + assert(handle != NULL); + handle->thread_num = thread_num; + + if (packet_io_config(profile, &(handle->config)) != 0) + { + goto error_out; + } + + handle->instance = marsio_create(); + if (handle->instance == NULL) + { + TFE_LOG_ERROR(g_default_logger, "%s: unable to create marsio instance", LOG_TAG_PKTIO); + goto error_out; + } + + if (marsio_option_set(handle->instance, MARSIO_OPT_THREAD_MASK_IN_CPUSET, coremask, sizeof(cpu_set_t)) != 0) + { + TFE_LOG_ERROR(g_default_logger, "%s: unable to set MARSIO_OPT_EXIT_WHEN_ERR option for marsio instance", LOG_TAG_PKTIO); + goto error_out; + } + + if (marsio_option_set(handle->instance, MARSIO_OPT_EXIT_WHEN_ERR, &opt, sizeof(opt)) != 0) + { + TFE_LOG_ERROR(g_default_logger, "%s: unable to set MARSIO_OPT_EXIT_WHEN_ERR option for marsio instance", LOG_TAG_PKTIO); + goto error_out; + } + + if (marsio_init(handle->instance, handle->config.app_symbol) != 0) + { + TFE_LOG_ERROR(g_default_logger, "%s: unable to initialize marsio instance", LOG_TAG_PKTIO); + goto error_out; + } + + // Netwrok Function Interface + handle->dev_nf_interface.mr_dev = marsio_open_device(handle->instance, handle->config.dev_nf_interface, handle->thread_num, handle->thread_num); + if (handle->dev_nf_interface.mr_dev == NULL) + { + TFE_LOG_ERROR(g_default_logger, "%s: unable to open device %s", LOG_TAG_PKTIO, handle->config.dev_nf_interface); + goto error_out; + } + + handle->dev_nf_interface.mr_path = marsio_sendpath_create_by_vdev(handle->dev_nf_interface.mr_dev); + if (handle->dev_nf_interface.mr_path == NULL) + { + TFE_LOG_ERROR(g_default_logger, "%s: unable to create sendpath for device %s", LOG_TAG_PKTIO, handle->config.dev_nf_interface); + goto error_out; + } + + return handle; + +error_out: + packet_io_destory(handle); + return NULL; +} + +// return n_packet_recv +int packet_io_polling_nf_interface(struct packet_io *handle, int thread_seq, void *ctx) +{ + struct acceptor_thread_ctx *thread = (struct acceptor_thread_ctx *)ctx; + struct global_metrics *g_metrics = thread->ref_metrics; + + marsio_buff_t *rx_buffs[RX_BURST_MAX]; + + // nr_recv <= rx_burst_max <= RX_BURST_MAX + int nr_recv = marsio_recv_burst(handle->dev_nf_interface.mr_dev, thread_seq, rx_buffs, handle->config.rx_burst_max); + if (nr_recv <= 0) + { + return 0; + } + + for (int j = 0; j < nr_recv; j++) + { + marsio_buff_t *rx_buff = rx_buffs[j]; + int raw_len = marsio_buff_datalen(rx_buff); + + if (is_downstream_keepalive_packet(rx_buff)) + { + marsio_send_burst(handle->dev_nf_interface.mr_path, thread_seq, &rx_buff, 1); + continue; + } + + if (marsio_buff_is_ctrlbuf(rx_buff)) + { + throughput_metrics_inc(&g_metrics->ctrl_pkt_rx, 1, raw_len); + // all control packet need bypass + handle_control_packet(handle, rx_buff, thread_seq, ctx); + marsio_send_burst(handle->dev_nf_interface.mr_path, thread_seq, &rx_buff, 1); + } + else + { + throughput_metrics_inc(&g_metrics->raw_pkt_rx, 1, raw_len); + handle_raw_packet_from_nf(handle, rx_buff, thread_seq, ctx); + } + } + + return nr_recv; +} + +void handle_decryption_packet_from_tap(const char *data, int len, void *args) +{ + struct acceptor_thread_ctx *thread = (struct acceptor_thread_ctx *)args; + struct acceptor_ctx *acceptor_ctx = thread->ref_acceptor_ctx; + struct packet_io *packet_io = thread->ref_io; + + struct addr_tuple4 inner_addr; + struct raw_pkt_parser raw_parser; + memset(&inner_addr, 0, sizeof(struct addr_tuple4)); + raw_packet_parser_init(&raw_parser, 0, LAYER_TYPE_ALL, 8); + raw_packet_parser_parse(&raw_parser, (const void *)data, len); + raw_packet_parser_get_most_inner_tuple4(&raw_parser, &inner_addr); + + struct session_node *node = session_table_search_by_addr(thread->session_table, &inner_addr); + if (node == NULL) + { + char *addr_string = addr_tuple4_to_str(&inner_addr); + TFE_LOG_ERROR(g_default_logger, "%s: unexpected inject packet, unable to find session %s from session table, drop !!!", LOG_TAG_PKTIO, addr_string); + free(addr_string); + return; + } + struct session_ctx *s_ctx = (struct session_ctx *)node->val_data; + + marsio_buff_t *tx_buffs[1]; + int alloc_ret = marsio_buff_malloc_device(packet_io->dev_nf_interface.mr_dev, tx_buffs, 1, 0, thread->thread_index); + if (alloc_ret < 0){ + TFE_LOG_ERROR(g_default_logger, "Failed at alloc marsio buffer, ret = %d, thread_seq = %d", + alloc_ret, thread->thread_index); + return; + } + + char *dst = marsio_buff_append(tx_buffs[0], len); + memcpy(dst, data, len); + + struct metadata meta = {0}; + meta.session_id = s_ctx->session_id; + meta.raw_data = dst; + meta.raw_len = len; + meta.is_decrypted = SET_TRAFFIC_IS_DECRYPTED(0); + meta.is_ctrl_pkt = 0; + meta.l7offset = 0; + meta.sids.num = 1; + meta.sids.elems[0] = acceptor_ctx->sce_sids; + + if (memcmp(&inner_addr, &s_ctx->c2s_info.tuple4, sizeof(struct addr_tuple4)) == 0) + meta.is_e2i_dir = s_ctx->c2s_info.is_e2i_dir; + else + meta.is_e2i_dir = s_ctx->s2c_info.is_e2i_dir; + + if (meta.is_e2i_dir) + { + route_ctx_copy(&meta.route_ctx, &s_ctx->raw_meta_e2i->route_ctx); + } + else + { + route_ctx_copy(&meta.route_ctx, &s_ctx->raw_meta_i2e->route_ctx); + } + packet_io_set_metadata(tx_buffs[0], &meta); + marsio_send_burst(packet_io->dev_nf_interface.mr_path, thread->thread_index, tx_buffs, 1); +} + +void handle_raw_packet_from_tap(const char *data, int len, void *args) +{ + char *src_mac = NULL; + char *dst_mac = NULL; + struct acceptor_thread_ctx *thread = (struct acceptor_thread_ctx *)args; + struct acceptor_ctx *acceptor_ctx = thread->ref_acceptor_ctx; + struct packet_io *packet_io = thread->ref_io; + + struct addr_tuple4 inner_addr; + struct raw_pkt_parser raw_parser; + memset(&inner_addr, 0, sizeof(struct addr_tuple4)); + raw_packet_parser_init(&raw_parser, 0, LAYER_TYPE_ALL, 8); + raw_packet_parser_parse(&raw_parser, (const void *)data, len); + raw_packet_parser_get_most_inner_tuple4(&raw_parser, &inner_addr); + + struct session_node *node = session_table_search_by_addr(thread->session_table, &inner_addr); + if (node == NULL) + { + char *addr_string = addr_tuple4_to_str(&inner_addr); + TFE_LOG_ERROR(g_default_logger, "%s: unexpected inject packet, unable to find session %s from session table, drop !!!", LOG_TAG_PKTIO, addr_string); + free(addr_string); + return; + } + struct session_ctx *s_ctx = (struct session_ctx *)node->val_data; + + marsio_buff_t *tx_buffs[1]; + int alloc_ret = marsio_buff_malloc_device(packet_io->dev_nf_interface.mr_dev, tx_buffs, 1, 0, thread->thread_index); + if (alloc_ret < 0){ + TFE_LOG_ERROR(g_default_logger, "Failed at alloc marsio buffer, ret = %d, thread_seq = %d", + alloc_ret, thread->thread_index); + return; + } + + char *dst = marsio_buff_append(tx_buffs[0], len); + memcpy(dst, data, len); + + struct metadata meta = {0}; + meta.session_id = s_ctx->session_id; + meta.raw_data = dst; + meta.raw_len = len; + meta.is_decrypted = 0; + meta.is_ctrl_pkt = 0; + meta.l7offset = 0; + + if (memcmp(&inner_addr, &s_ctx->c2s_info.tuple4, sizeof(struct addr_tuple4)) == 0) + { + meta.is_e2i_dir = s_ctx->c2s_info.is_e2i_dir; + src_mac = s_ctx->client_mac; + dst_mac = s_ctx->server_mac; + } + else + { + meta.is_e2i_dir = s_ctx->s2c_info.is_e2i_dir; + src_mac = s_ctx->server_mac; + dst_mac = s_ctx->client_mac; + } + + if (meta.is_e2i_dir) + { + sids_copy(&meta.sids, &s_ctx->raw_meta_e2i->sids); + route_ctx_copy(&meta.route_ctx, &s_ctx->raw_meta_e2i->route_ctx); + } + else + { + // raw_meta_i2e->raw_data maybe is null + sids_copy(&meta.sids, &s_ctx->raw_meta_i2e->sids); + route_ctx_copy(&meta.route_ctx, &s_ctx->raw_meta_i2e->route_ctx); + } + + + packet_io_set_metadata(tx_buffs[0], &meta); + add_ether_header(dst, src_mac, dst_mac); + marsio_send_burst(packet_io->dev_nf_interface.mr_path, thread->thread_index, tx_buffs, 1); +} diff --git a/common/src/tfe_utils.cpp b/common/src/tfe_utils.cpp index 4e8ef84..0ae3492 100644 --- a/common/src/tfe_utils.cpp +++ b/common/src/tfe_utils.cpp @@ -376,7 +376,7 @@ int get_mac_by_device_name(const char *dev_name, char *mac_buff) } unsigned char *mac = (unsigned char *)ifr.ifr_hwaddr.sa_data; - sprintf(mac_buff, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + memcpy(mac_buff, mac, 6); close(fd); return 0; diff --git a/common/test/test_mpack.cpp b/common/test/test_mpack.cpp new file mode 100644 index 0000000..65fdc93 --- /dev/null +++ b/common/test/test_mpack.cpp @@ -0,0 +1,16 @@ +#include +#include + +#include "tfe_ctrl_packet.h" + +// TEST(MPACK, PARSE) +// { +// struct ctrl_pkt_parser handler; +// parse_messagepack(); +// } + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/common/test/test_session_table.cpp b/common/test/test_session_table.cpp new file mode 100644 index 0000000..a804dc0 --- /dev/null +++ b/common/test/test_session_table.cpp @@ -0,0 +1,274 @@ +#include +#include + +#include "tfe_session_table.h" + +TEST(STREAM_TABLE, INSERT) +{ + // TEST Create + struct session_table *table = session_table_create(); + EXPECT_TRUE(table != nullptr); + + char *val_hello = strdup("HELLO"); + char *val_world = strdup("WORLD"); + INIT_ADDR_V4(addr1, "1.2.3.4", 1234, "4.3.2.1", 4321); + INIT_ADDR_V6(addr2, "2:3:4::5", 2345, "5:4:3::2", 5342); + + // TEST Insert + EXPECT_TRUE(session_table_insert(table, 1, &addr1, val_hello, free) == 0); + EXPECT_TRUE(session_table_insert(table, 1, &addr1, val_hello, free) == -1); + EXPECT_TRUE(session_table_count(table) == 1); + + EXPECT_TRUE(session_table_insert(table, 2, &addr2, val_world, free) == 0); + EXPECT_TRUE(session_table_insert(table, 2, &addr2, val_world, free) == -1); + EXPECT_TRUE(session_table_count(table) == 2); + + // TEST Destory + session_table_destory(table); +} + +TEST(STREAM_TABLE, SEARCH_BY_ID) +{ + // TEST Create + struct session_table *table = session_table_create(); + EXPECT_TRUE(table != nullptr); + + char *val_hello = strdup("HELLO"); + char *val_world = strdup("WORLD"); + INIT_ADDR_V4(addr1, "1.2.3.4", 1234, "4.3.2.1", 4321); + INIT_ADDR_V6(addr2, "2:3:4::5", 2345, "5:4:3::2", 5342); + INIT_ADDR_V4(addr3, "1.1.1.1", 1111, "2.2.2.2", 2222); + + // TEST Insert + EXPECT_TRUE(session_table_insert(table, 1, &addr1, val_hello, free) == 0); + EXPECT_TRUE(session_table_insert(table, 2, &addr2, val_world, free) == 0); + EXPECT_TRUE(session_table_count(table) == 2); + + // TEST Search By Session ID + struct session_node *node = NULL; + node = session_table_search_by_id(table, 1); + EXPECT_TRUE(node != nullptr); + EXPECT_STREQ((const char *)node->value, "HELLO"); + node = session_table_search_by_id(table, 2); + EXPECT_TRUE(node != nullptr); + EXPECT_STREQ((const char *)node->value, "WORLD"); + node = session_table_search_by_id(table, 3); + EXPECT_TRUE(node == nullptr); + + // TEST Destory + session_table_destory(table); +} + +TEST(STREAM_TABLE, SEARCH_BY_ADDR) +{ + // TEST Create + struct session_table *table = session_table_create(); + EXPECT_TRUE(table != nullptr); + + char *val_hello = strdup("HELLO"); + char *val_world = strdup("WORLD"); + INIT_ADDR_V4(addr1, "1.2.3.4", 1234, "4.3.2.1", 4321); + INIT_ADDR_V6(addr2, "2:3:4::5", 2345, "5:4:3::2", 5342); + INIT_ADDR_V4(addr3, "1.1.1.1", 1111, "2.2.2.2", 2222); + + // TEST Insert + EXPECT_TRUE(session_table_insert(table, 1, &addr1, val_hello, free) == 0); + EXPECT_TRUE(session_table_insert(table, 2, &addr2, val_world, free) == 0); + EXPECT_TRUE(session_table_count(table) == 2); + + // TEST Search By Session Addr + struct session_node *node = NULL; + node = session_table_search_by_addr(table, &addr1); + EXPECT_TRUE(node != nullptr); + EXPECT_STREQ((const char *)node->value, "HELLO"); + node = session_table_search_by_addr(table, &addr2); + EXPECT_TRUE(node != nullptr); + EXPECT_STREQ((const char *)node->value, "WORLD"); + node = session_table_search_by_addr(table, &addr3); + EXPECT_TRUE(node == nullptr); + + // TEST Destory + session_table_destory(table); +} + +TEST(STREAM_TABLE, SEARCH_BY_REVERSE_ADDR) +{ + // TEST Create + struct session_table *table = session_table_create(); + EXPECT_TRUE(table != nullptr); + + char *val_hello = strdup("HELLO"); + char *val_world = strdup("WORLD"); + INIT_ADDR_V4(addr1, "1.2.3.4", 1234, "4.3.2.1", 4321); + INIT_ADDR_V6(addr2, "2:3:4::5", 2345, "5:4:3::2", 5342); + struct addr_tuple4 addr1_reverse; + struct addr_tuple4 addr2_reverse; + addr_tuple4_reverse(&addr1, &addr1_reverse); + addr_tuple4_reverse(&addr2, &addr2_reverse); + + // TEST Insert + EXPECT_TRUE(session_table_insert(table, 1, &addr1, val_hello, free) == 0); + EXPECT_TRUE(session_table_insert(table, 2, &addr2, val_world, free) == 0); + EXPECT_TRUE(session_table_count(table) == 2); + + // TEST Search By Session Reverse Addr + struct session_node *node = NULL; + node = session_table_search_by_addr(table, &addr1_reverse); + EXPECT_TRUE(node != nullptr); + EXPECT_STREQ((const char *)node->value, "HELLO"); + node = session_table_search_by_addr(table, &addr2_reverse); + EXPECT_TRUE(node != nullptr); + EXPECT_STREQ((const char *)node->value, "WORLD"); + + // TEST Destory + session_table_destory(table); +} + +TEST(STREAM_TABLE, DELETE_BY_ID) +{ + // TEST Create + struct session_table *table = session_table_create(); + EXPECT_TRUE(table != nullptr); + + char *val_hello = strdup("HELLO"); + char *val_world = strdup("WORLD"); + INIT_ADDR_V4(addr1, "1.2.3.4", 1234, "4.3.2.1", 4321); + INIT_ADDR_V6(addr2, "2:3:4::5", 2345, "5:4:3::2", 5342); + struct addr_tuple4 addr1_reverse; + struct addr_tuple4 addr2_reverse; + addr_tuple4_reverse(&addr1, &addr1_reverse); + addr_tuple4_reverse(&addr2, &addr2_reverse); + + // TEST Insert + EXPECT_TRUE(session_table_insert(table, 1, &addr1, val_hello, free) == 0); + EXPECT_TRUE(session_table_insert(table, 2, &addr2, val_world, free) == 0); + EXPECT_TRUE(session_table_count(table) == 2); + + // TEST Delete By Session ID + EXPECT_TRUE(session_table_delete_by_id(table, 1) == 0); + EXPECT_TRUE(session_table_search_by_id(table, 1) == NULL); + EXPECT_TRUE(session_table_search_by_addr(table, &addr1) == NULL); + EXPECT_TRUE(session_table_search_by_addr(table, &addr1_reverse) == NULL); + EXPECT_TRUE(session_table_count(table) == 1); + + EXPECT_TRUE(session_table_delete_by_id(table, 2) == 0); + EXPECT_TRUE(session_table_search_by_id(table, 2) == NULL); + EXPECT_TRUE(session_table_search_by_addr(table, &addr2) == NULL); + EXPECT_TRUE(session_table_search_by_addr(table, &addr2_reverse) == NULL); + EXPECT_TRUE(session_table_count(table) == 0); + + // TEST Destory + session_table_destory(table); +} + +TEST(STREAM_TABLE, DELETE_BY_ADDR) +{ + // TEST Create + struct session_table *table = session_table_create(); + EXPECT_TRUE(table != nullptr); + + char *val_hello = strdup("HELLO"); + char *val_world = strdup("WORLD"); + INIT_ADDR_V4(addr1, "1.2.3.4", 1234, "4.3.2.1", 4321); + INIT_ADDR_V6(addr2, "2:3:4::5", 2345, "5:4:3::2", 5342); + struct addr_tuple4 addr1_reverse; + struct addr_tuple4 addr2_reverse; + addr_tuple4_reverse(&addr1, &addr1_reverse); + addr_tuple4_reverse(&addr2, &addr2_reverse); + + // TEST Insert + EXPECT_TRUE(session_table_insert(table, 1, &addr1, val_hello, free) == 0); + EXPECT_TRUE(session_table_insert(table, 2, &addr2, val_world, free) == 0); + EXPECT_TRUE(session_table_count(table) == 2); + + // TEST Delete By Session Addr + EXPECT_TRUE(session_table_delete_by_addr(table, &addr1) == 0); + EXPECT_TRUE(session_table_search_by_id(table, 1) == NULL); + EXPECT_TRUE(session_table_search_by_addr(table, &addr1) == NULL); + EXPECT_TRUE(session_table_search_by_addr(table, &addr1_reverse) == NULL); + EXPECT_TRUE(session_table_count(table) == 1); + + EXPECT_TRUE(session_table_delete_by_addr(table, &addr2) == 0); + EXPECT_TRUE(session_table_search_by_id(table, 2) == NULL); + EXPECT_TRUE(session_table_search_by_addr(table, &addr2) == NULL); + EXPECT_TRUE(session_table_search_by_addr(table, &addr2_reverse) == NULL); + EXPECT_TRUE(session_table_count(table) == 0); + + // TEST Destory + session_table_destory(table); +} + +TEST(STREAM_TABLE, DELETE_BY_REVERSE_ADDR) +{ + // TEST Create + struct session_table *table = session_table_create(); + EXPECT_TRUE(table != nullptr); + + char *val_hello = strdup("HELLO"); + char *val_world = strdup("WORLD"); + INIT_ADDR_V4(addr1, "1.2.3.4", 1234, "4.3.2.1", 4321); + INIT_ADDR_V6(addr2, "2:3:4::5", 2345, "5:4:3::2", 5342); + struct addr_tuple4 addr1_reverse; + struct addr_tuple4 addr2_reverse; + addr_tuple4_reverse(&addr1, &addr1_reverse); + addr_tuple4_reverse(&addr2, &addr2_reverse); + + // TEST Insert + EXPECT_TRUE(session_table_insert(table, 1, &addr1, val_hello, free) == 0); + EXPECT_TRUE(session_table_insert(table, 2, &addr2, val_world, free) == 0); + EXPECT_TRUE(session_table_count(table) == 2); + + // TEST Delete By Session Reverse Addr + EXPECT_TRUE(session_table_delete_by_addr(table, &addr1_reverse) == 0); + EXPECT_TRUE(session_table_search_by_id(table, 1) == NULL); + EXPECT_TRUE(session_table_search_by_addr(table, &addr1) == NULL); + EXPECT_TRUE(session_table_search_by_addr(table, &addr1_reverse) == NULL); + EXPECT_TRUE(session_table_count(table) == 1); + + EXPECT_TRUE(session_table_delete_by_addr(table, &addr2_reverse) == 0); + EXPECT_TRUE(session_table_search_by_id(table, 2) == NULL); + EXPECT_TRUE(session_table_search_by_addr(table, &addr2) == NULL); + EXPECT_TRUE(session_table_search_by_addr(table, &addr2_reverse) == NULL); + EXPECT_TRUE(session_table_count(table) == 0); + + // TEST Destory + session_table_destory(table); +} + +TEST(STREAM_TABLE, RESET) +{ + // TEST Create + struct session_table *table = session_table_create(); + EXPECT_TRUE(table != nullptr); + + char *val_hello = strdup("HELLO"); + char *val_world = strdup("WORLD"); + INIT_ADDR_V4(addr1, "1.2.3.4", 1234, "4.3.2.1", 4321); + INIT_ADDR_V6(addr2, "2:3:4::5", 2345, "5:4:3::2", 5342); + + // TEST Insert + EXPECT_TRUE(session_table_insert(table, 1, &addr1, val_hello, free) == 0); + EXPECT_TRUE(session_table_insert(table, 1, &addr1, val_hello, free) == -1); + EXPECT_TRUE(session_table_count(table) == 1); + + // TEST Reset + session_table_reset(table); + EXPECT_TRUE(session_table_search_by_id(table, 1) == nullptr); + EXPECT_TRUE(session_table_search_by_addr(table, &addr1) == nullptr); + EXPECT_TRUE(session_table_count(table) == 0); + + EXPECT_TRUE(session_table_insert(table, 2, &addr2, val_world, free) == 0); + EXPECT_TRUE(session_table_insert(table, 2, &addr2, val_world, free) == -1); + EXPECT_TRUE(session_table_search_by_id(table, 2) != nullptr); + EXPECT_TRUE(session_table_search_by_addr(table, &addr2) != nullptr); + EXPECT_TRUE(session_table_count(table) == 1); + + // TEST Destory + session_table_destory(table); +} + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file diff --git a/platform/src/acceptor_kni_v4.cpp b/platform/src/acceptor_kni_v4.cpp index 329d8bc..68a6613 100644 --- a/platform/src/acceptor_kni_v4.cpp +++ b/platform/src/acceptor_kni_v4.cpp @@ -63,22 +63,22 @@ static void *worker_thread_cycle(void *arg) handle_raw_packet_from_tap(thread_ctx->tap_ctx->buff, pkg_len, thread_ctx); } - if ((pkg_len = tfe_tap_read_per_thread(thread_ctx->tap_ctx->tap_c, thread_ctx->tap_ctx->buff, thread_ctx->tap_ctx->buff_size, g_default_logger)) > 0) - { - handle_decryption_packet_from_tap(thread_ctx->tap_ctx->buff, pkg_len, thread_ctx); - } + // if ((pkg_len = tfe_tap_read_per_thread(thread_ctx->tap_ctx->tap_c, thread_ctx->tap_ctx->buff, thread_ctx->tap_ctx->buff_size, g_default_logger)) > 0) + // { + // handle_decryption_packet_from_tap(thread_ctx->tap_ctx->buff, pkg_len, thread_ctx); + // } - if ((pkg_len = tfe_tap_read_per_thread(thread_ctx->tap_ctx->tap_s, thread_ctx->tap_ctx->buff, thread_ctx->tap_ctx->buff_size, g_default_logger)) > 0) - { - handle_decryption_packet_from_tap(thread_ctx->tap_ctx->buff, pkg_len, thread_ctx); - } + // if ((pkg_len = tfe_tap_read_per_thread(thread_ctx->tap_ctx->tap_s, thread_ctx->tap_ctx->buff, thread_ctx->tap_ctx->buff_size, g_default_logger)) > 0) + // { + // handle_decryption_packet_from_tap(thread_ctx->tap_ctx->buff, pkg_len, thread_ctx); + // } } - global_metrics_dump(acceptor_ctx->metrics); + - if (n_pkt_recv_from_nf == 0) - { - packet_io_thread_wait(handle, thread_ctx, 0); - } + // if (n_pkt_recv_from_nf == 0) + // { + // packet_io_thread_wait(handle, thread_ctx, 0); + // } if (__atomic_fetch_add(&thread_ctx->session_table_need_reset, 0, __ATOMIC_RELAXED) > 0) { diff --git a/platform/src/proxy.cpp b/platform/src/proxy.cpp index df4285a..e09d3e0 100644 --- a/platform/src/proxy.cpp +++ b/platform/src/proxy.cpp @@ -60,6 +60,7 @@ /* Systemd */ #include +#include "tfe_acceptor_kni.h" extern struct tcp_policy_enforcer *tcp_policy_enforcer_create(void *logger); extern struct chaining_policy_enforcer *chaining_policy_enforcer_create(void *logger); @@ -292,6 +293,8 @@ static void __gc_handler_cb(evutil_socket_t fd, short what, void * arg) FS_operate(ctx->fs_handle, ctx->fs_id[i], 0, FS_OP_SET, ATOMIC_READ(&(ctx->stat_val[i]))); } + // global_metrics_dump(ctx->kni_v4_acceptor->acceptor->metrics); + FS_passive_output(ctx->fs_handle); return; } diff --git a/vendor/CMakeLists.txt b/vendor/CMakeLists.txt index c750568..ea7d58d 100644 --- a/vendor/CMakeLists.txt +++ b/vendor/CMakeLists.txt @@ -379,7 +379,7 @@ set_property(TARGET libnetfilter_queue-static PROPERTY INTERFACE_INCLUDE_DIRECTO ### msgpack-c 6.0.0 ExternalProject_Add(msgpack-c PREFIX msgpack-c URL ${CMAKE_CURRENT_SOURCE_DIR}/msgpack-c-6.0.0.tar.gz - URL_MD5 f930a80b118a20de2be3211b0706f562 + URL_MD5 adc08f48550ce772fe24c0b41166b0de CMAKE_ARGS -DCMAKE_INSTALL_PREFIX= -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DMSGPACK_BUILD_TESTS=OFF) diff --git a/vendor/msgpack-c-6.0.0.tar.gz b/vendor/msgpack-c-6.0.0.tar.gz index 4a4a47ace4e6a568b3cc2fd555c63c043d89a474..185e89c4ef24dfd2acdb634c6cf963c83c5defbe 100644 GIT binary patch literal 69341 zcmV(aA9L>En_VDtXQRUbyC1)* zuCA)CeyEv+ALpiZvuSO1wi;WFZRggU&8N=x50CgX@YCJhg}=@2Zi9Spw%g<{dw#&* z8;wq@(`+?=z?lu4jdmZC3Ei^{BXfb!4>y5t2f5{(Mfem9J?%5o^#9?BdE*SdFbcP# z+vs65po2TKzg7Lev)kBB^?#?+==^{hkJH5b=l^;Cza5N@jI-Y1FTLYFs#^a#KONQ9 z)`o-g3smi$o%KgY$hmba<7|9-Jno&GuOa+Qf~9Ypi@Pn0e4Tm|W3qHzXJIUT&?c51 zdZ0>%#lOxS$2KA-jEtq{N39*hTrA8xb#A9E<%0AxG-_*uw~S}w?dj#{s7A`_nMZ$w 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

-Hu$Wv1Y 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 DK+J(f literal 68965 zcmV(|K+(S+iwFn;(?4VY18s9>aA9L>En_VRwK!Bv({JS)l7Fw zAc0;$H+18phJveU5LiHWj}fPCNh(2Iq$*84x`Dphd*OvQ-r47#4xhX*Gv6-w1ES>+Gu2rf3*&St&D%I(x|O`M=For+mz@3`S^ct z+5J&4Fv)Aq2}>87x!fnm8W0@(LNWIxN8s_S8&mh`4|zkXeCRf49?L9 z2L6Hc1v0tZkf8+=$a6dZ7=skpw8{VzHgai==$38yh3D{5

    |07q?6x6RMAOes!QK$t5_!%?qe$Mo zA^S(Whs~{b`bnGq9Gz@!X-7vzPKRj*A_L$U*SQwBqzzkGgpC|P0wR#HVc7*_I>Bz3 zXhj4T#0An;E0nNB&YmpaHT}S~vGg?eYtN|P){`awxAu)obIUNk`Z=O+*CKYUTDP8sZ7?O5Mw z?P=}izNT*->>nPqwf3=6Q~!*WrGzP=r9Ws4M00yv*FI`nC&$gVdqDGWeNZX^lxcbn zOJ=Z+n;pbxuho7Bf8KVQotAb)3fYjt=`I05D&&9ovbEoQ2YsTwf*yAC*7h_{j*dIp zU!OGhNPcT%*ynk2&>^=dyxTs>M=ReE;0S~~)H=sMkpe?H*O2V>E)lu$;LDZzUScJNM}8p=mqivE_{ozkgv4%D!>#Jp|kRqxV}9vRF%0 zVNLEgkGFmg!=GRNa9Sxh&ho12!SF3~v|a5Z>1c<0%`Gh|i*I$Z{NZDss8A)$Xu?zi ztL+_W*bSu_?YJ-p0ufC~kOW1P4|oaZCJ-xiwr5NMG1FoEwrG_wZ>rR5wYT<8wl#gb z)zP+&53oQ~_psA?-#per7*b&b6XN-63-Jd$S?hnAz`bu9bJqVxwHmYks%tCtr}h7v zJgN3yl<4^b%TbdI4_L#eX5#k)b7y0h%@DUzywL5e*rTN@bUZwLdTHX)Faat%&<&zh~*Jj>z%&gn*-9j zCjF6R+Wt};-_Tv)nqDyS6(55$9A^0!bZ$1n&qkWnN%0JgTgG3fX}o@x?!Dl=%8-gI zRmn}ESmvx;iID_o0VX*h&+BoMo>re{p3AuK0frxFmbE}Fe?GZ5txr4?i-d&5XW{}m zhK2kAgJ3)4*u`CC zxjPMI4zT#OHJPX9K2MhZA9+e1-o|YGzg}5s)MNVp%1XWVr2l`5XQrwTNh7NlPXmE_ zuBL%xy@;ix854bEs%2`yGxGt2J+mMN1%3Z$OMkC*iZoSxk=_hyqs$BR7*{LN%ELt+5>ASKxH&Ti>Ua%(>iT!Pv1Y-)&OUe z@{k2ur_B~5OyX^cB%()|0wOQ6t-U?jEi5f96~%g{>+hR-v$K1o>&09Sc+|K0Od(xh zOPS6FqCoX%o@3dtB8V-NScdOPO6`I}7ms*~C?iy7T1W>);@rQ8)M8kB>|oq8T@N)D zV=%CM%dw3S1!VMn$lyv2hhutuV`A!2HiH%F85C_?@!KbRd-B@Ompn`si*~dfMEeAg zXzdNrM?!xV7iBQsXUp$jkOC_r=kyIwIbL-0FV^Ep-DFZPnKVo$*~ug~ndD1JoTjKN zh9u%NBcAzlVA_3CRvW3V6esE8;)YV7=bFZ4lmvWcfOfY&tISl=#o|L>U_)8tR4+5C z7s+B06F#sE0#;@GRUA{@x}ZW{3=L~!1X1VqoPfOOAP**)4*{ld9gN#Y#I@TGOe5keds%rYr-b980l;tC%@wj5~j9t zjxTZOD(@o|IOCH|Qm+=t^XFnFFhKbi$+Yf^q6{RYGzcDPnwJ>fG1goNJ1L7DN(83j zC*Ve0Z=2@8Lp!Wza>g2DfL~lB3{jQsV^dwpQ`=3M=gA%<8^9+vZzO2>IneTVg4UZ2 zt#>D2!`Z-wcLHY524>$0m^&ModnaIV!5RbR-ws%+xXdUUZLSFcD#^cDq9P_Hs8yFE z3*-#u!Mnp<#{8K@R5$R};{x1uN78!G{_bH{S{| z2kQd*?pQMmwlgs0qZiN^SgAK454noKiuEt7(Lh!jKxBb=9hlN$j-X~p@N!iVh+@8C zt@0*nT;w^aT)O^Rz3LHN0HVr*y1+H6&c~=l^H_?sMBx~O6@Ql;2aZ+jtQ7SZ!&qD}r!jS}b0G8$ z_&0=FEUFWL9#0B0H{vZR9#%T^Y0E&2JtP*9X-n14b)~PCXB)Omk%1HRMrM)x=}*M+ zEPI%ecME!igA~JBu($#c`CUqc8FY&l&`pYTj#Zpbr#K*nLmU?GYFxdG4@#WGP4xOp zEO-O0v7BAgrxD|75;NwJy8-4@6!W<}zC4Yo|^G0f4B59~|Z`D_zhaF^8Gn8Z4* zf>pH??1->iKx^m2!dZj5<1=)Za#hw_s2g4$QiO~9o2e>`7|B4)zCN%!7&V6*#OCc@7Pi6tg!=W#WTH znTD9I>$vMAu+6Ip@C_J7obG>k=KX>0a{=6W*i&s1AU^Oojg0!POkeXF^*SD-5KFtGb2Vgy&=eq{8nvt!iMiPsgNQBFdg z!pthNuWutZ_q!pNAhg%Cd-r>~Y3u(3APJl{;~lyKf(e`|_rpj)iZB7fOCO7O=xiL4 z&hI?LQ_2WaNW(41{$#p-ngm3_D9K4#AZ}LHBasO2+$rV^#U7jO_%N-V;QRtb=DK3* zu^E%Pc`n2!6Lf;_qEi;oh>)Z4a{{6Fm{wKj$zBu>ags_5x>6|8GljM1EaAeVn0R8d z_u%c1+Sak&Zb?Q|)@&5+^f@y!ZwI51yu@)MKC|(+2M2pvv;AlYKz~13#!}C5My6qt zzan`wAM<3L&7!CDOcr z643~C-HKe02Pb<6?OlDp`7thuAB@c?fORecKgU*joaPFF?OEqQTrzS%wUx;bwk|ew zZaau=ZMQjd5Us}_L@OFZYvv$YbP&JZ7~(Os>>*U~c+E?;+Fuh7fd18430o^98OeGW zBkk-RG>_}G_1Ohh8;>VUpAGYZjfM*D^H7E69uJFd{@K!k*UckQ(QCpGf;Ep{W{bXY z8{iJ2ztvXVODPKNb1u$7dyO+C*Vyrt46}B5MUP-j)zHFcrVamfXas00G{Wz@(MbDv zoG>U+QSpMw!~q=$*Y@?4O4){HW>d{hr}@+4am{Hy=5S#oU>A+30+VKf$5f<+3GUxX z6_q`%v9YHl6Og}%Hr}|LqUS(J}X%hmXhCU()${Y)o#;)%xWpnX$3v9JZ!4QaGDe4f$Fi zv5~tMhf$|YdGpe|rZxk1CAqGoRm4XU#{dBBD$6DY0hGu%0K|KyFv^mmSv(oeHMP7AHX?VJySMGq`Zp2?6S_ zuP@yYdOrV(5I%_=1>rxTK=?_g*V!U;)~RjrM0XO_!!zl!SBQFWk>05qV$yY)Bq=*) z9=b5WxS~V7r?pA7QfnlrMK34ZB&itP7>V+@Cx_DoSPjIz+(23Ou2QzZqSVPeIF$;X zNvnX22OqPWlGH@X0M9r{U!vY~81fsu^q^b^QLvurFN=_t5s6JgWj(YZ`xy;LJuuEso( zsa4ZQIGh|esrON`=8$|zL&z8^r-8?(1fhpbmdKapd@DIGfGRl*uG^uoof6va+ zApRUUzR6nMzoulzOBlz_m=wC*ZvL%l;!lyhb9!bvPV7t_0|8~by>55d?b_Y0+wJO$9+bDCG%HM@#9=74K?A~aKmkOT_Bab)=svD_w2U=2&RG&pb4UUJpRIv^0k8Gd z3Z!3{*7*fAx89(izU7Zt)%TY9nT%OwYXau)`f0VgT6)!}muf4krL}7HY&uQrC^f+7 zcy;qf7~kTW!q|7h*JBt$@&3Ua*wNSVMc{@XJFX>Oj|`CB^dogZ86T0T<_LYdo25B{?Yk`bYi)Z6eqOz7zJ#CEwF>^MG%8#8^Q!hraO5w4`O7c=@lXHt5C8tlpa1;d|NO82 z`Hz45%isUqFaPpyan=m{;mf%-Qjlc_Yp`auwp4F~FL;+FAKN7T-ye|Zmhs=Ba4h#| zV_y8XMrAb-|E>NM|LvPRw~hbCnZPHEB~}}_JAbvavm*s@tW?G6{HJJf{CVVPajOm5 zlo*5GSkSmsJf334{i;uv{O^yA%g2fWd-wRSpwvCZfBh!U?D2n`D1h_U|4J=>|97ok zUwvBtzr{nxSqCNF8V6(DHGd8)7go~3d#!WSI%t#nQl*%ixDNFR6*zYkK>`m8+Q;Vp z;hv@ov9ImF)wZ`a9+%4)40JE(4FFuG_6Fmi$kN>3Q!q=E(oGN~4;T|CJ~C|1F*e#ed78!wz?rKtB)q zc+#_PTA#pno3|Gfo(%Qy;$v8EgIk{Bn;9t~)P-;-bzZDE)$DW2o6?R<_HGd`_Y_m^ zw-8f~nsDV!WFZW(#fEN4?0Duv4&$5zFP~&iD$#8!HU6CMUh@O&%B2JrK~8P~&wjI} z#ldEN9J=oZ#z-867T}_b9&-Ms08xxpQz7&C1Qbny0zFl}hxGmuS9dUSth825-)H~3^3)IFFbzg zoq*IGmk3z$=G8>RL<_+i{l4Q4a7*SoByk)Ev555z__qA(LWmN1t#l(TcwPtu2x_{Y z%ve-9poan>a_=x1c^70h-=x73S5z3eXec>_pG!$pV<;A*;Yoc`8qLI-NBdH`&oEP6 zD$(#tS86bduinwN-m~bS4K*aB#AI_qK#In*fX{7{lxYL+KXi4w_uakv(!m(G%mpiI?LjLbCnVeF(s1cdGkvok3;2_eV8 zNY!|ejGdLD(S%Z?G~w;IFcA+>?|!J)FjSFfN$eGKJP9SrI(arA&sqa^O-%G*zz#n| zw8K~?5@*qK>WJW@VsUe4EYLi<9~x;P8!1Cx`8b(S^^iwj;(%Mn`;$nOiE=1n`RCKM{95tj(@BGvgtYAHqL z$L5Rysn2hxF|!>x!e^}F3iKkoVk`*gQ&`Xc>ra;dC;AsJ4{u{m{Li)3m1O+S+S-%< z=UY4=70>&9=|B3AvJ-xh@`paX*z8%b?v_7H9FWhZTgGc_C`BLSJPM-*jLVQ9s5pw~HVO&SGPly&qU?>BT+2~QUPHSX= zSZ82uzb1_$9#2nXBGJ!cMhb0XM%0@K)wmv_toezfqw`~X3)jV!LMdlB^i0X9qlBkj zUR_zKug3lX0P2k}kmCvQy;D3H9QEM~J2gX}5)Aybq-2SsGL@9P`iy#w%zCZ%@oEFF z+Lp)(yn|r^?}B0*k$n!9UqqKe?{+~PSgeGB1MS@BiNZfbybILgL3_UMC8A+uRp*ni zAbaT8kQIT$YN%9GD{gl6Xr{&7Y#!eD_`X+S|Dl4~%^OnnkU1h3&MnRMT&MU_tt)z@=w;!mMEj#MK);Wkk_cT4)7dALN~&8|g@w`klHlk~j{}zhAzJjy#)?GDV3xeeci`ZF8e*Ny>31$&b?_n^KQ?4V#puWc|GMrw#xO zbfXVSw46iyBr@3z6bgkxp-?DP37|?oSfKaCN8;)UT(dFW-A=%`zY+N;%=-rBd3zxm zQoTs-O+WgPz3Lmiiu5qvata%pOia)d(AW5PFOKV8UxNcc_Uy#gfIP9ysV5teN$?~S znMEd(9Z=FtiqIyVG;;oA&?cQU5=aJZGD#Co=2Y6G${8IFS%u?rrmD>akTKOf^jS>4 z*kL6JmxTsQ3X?kxm;`dy0ShJD-3Cm;S;Bzn!uAdeLIf)tULN_lg^O0WC{$C#>9Q6C zJLII*U?CQhlKxICCV||Q#lmIhZY(C@EP=)JmGF)%reU>Q7K^UdOVK%E7~8Rynn%^p zJi>9(^^a<%{_%Bm57c^6HHY4Yp*wKbdK*boLWj480N1J(kWV6{0(c2iL%<7{ZwoPc z)xEx{QS_>aUy4SlK&~-N83Mc~OGpQP%J#+sEdY`x7rZMYP+n*!T(Em$?M7Per+ zd-I9Gj@-bL9NMoT5;GXEx3yF5&ml#&hP8A(iT>5yCodLq~GLVX6)Q`+<0VrXd^6yh5pagEqzgLlf_7ddR zk|(kb?uiXlzaLQz031*&K^hw1;#7-pRwipHSUGqri?u>n*}Rp?S_)7O?n-5?ocp|N z*2=lpGg&JQP}W{p07_Qy^6&XHSecZ{((x3mG(|6Kaqae^iz|TJOI9ep=qjZHhx#_K zxD*)}E8x8TB!8gMH;|Vk(AS_VS$-R$y}mIhnXRw za#}kqV3W1Z#VsESZ1&zX&u*aW*>(c}d@pO)c3;YJO2frs_3-e;i>+$uakXS=QEm&2 zE^>qVb|7oxqcteo%UnxL9>}+>C~_{ateB1DSYGr-@-8oWBN@v~2tNNZV}egzZZzz3 zFE={)v}H%ZUSQcVVJ9y?A?*Cij|Mw=8B%~3ScWvfS$wkCa-;#y;*iCbB@J*EZ!ESv zX@FCgshpP46xf-RUTC?}VP`B`IW4Fu;B!dE#g;D}e6D3|K?|#;ku3Ai9j&d4n_3q! zbaneyLl-xiV&saqiYeKy8RBrh5rCxExQ3JfU+LJu00kHzwP?Kvf^F9vHRZLEgF;sm!cQ zVfq)SodCiDCk`k=*BDt-C>s*i?#$I0<#XryojG%TZEq||%f3Gt(8XA2%p5X#9Tvum zoiazliOYh5WyEJwsinZkuB^ZSMrN%m-nsTEF92-GYs3oCt(ZQAERdvnpZ76mRcZ^H zu@*KhE%csl+Bc`Yr_Sa^RwJ7`JJD9?k);{?LJ6((GrO|6vm0+^*Vc+_QC_eWS1gbj z4WV0TtV80VrLoxR&rR`an4_s!uf)T6Q|B?I2AeuJR({c-IR!8uY>MIY#3|-krkC1zk-EDf^ZED%4)l#PvQzM*IKm8J)R8N&R&QtreQb3G} z#ZPTh>#1#OA^7Bp6*jSBX~V{hPwnRwPm}pDY-88bhLsDu@#%$Z*v*(+s-@WU;x~yC ztQ(nN+*tbbG8(h=EOCnU;to%}UiJh#_eU``4xS~banWK44%FMxkH)NgG zdmA?95)JKSG*sP77==A+Q`M@~jS5<-Rx?@xIh!zF>;ibtaklDqac6Gie9J^(cJ187s7`sHXdp`VGe`Kgr6s$FdJsUe%w=9Id| zi3rSs_`%w)Vbo^Hlfzvz0vGSU#$oUEZ3cUmIsL`V_3`#%l3Qm~TWhtsaH|^xXmUtS2Vtx{5CK?3rv3!__%Fc^vr&e+<15Ms^sFVym2#4aQAx z3179z`Ghf~(`+n5JR$Horlg9&qHEty*BuPb;jz=7kGj^jh0MvFF)wKc|2qqI8ZwME zSDZ#)ZB%ut5|twi-~+$Aqiz^v%&e~Bo&pDO)UDnevx*T|gUci!2TEOeu`$u|;R${O z9dBd%i9M4?dNMo(;9DJ2t^Y(MLebKd7;4rKZxP5u`uQvB({ZxNTJJe8Sq=gU=3?{-MqwY6l0M`iJ`dS-bZ7ur5f&L@}X!w!h)W z!eY$XM>>a%t>C~pA1S#0u4Bi((9AzXJjuV2KtCX`?MM zl)Xjp7M$W8<|>UhPH{!0MEUT6=nOP(b{YqtSDaQGnXh)}tn3fm(Zz~$(sXhae|vnk z67EKT)o^f6>&^C$j?Cf`$FX*LT89Y$P#VW?9aTZJ(Rp)Nd)snWoV6c73x~hn^=#@I z0rf_o(%LUqct`g_XG3qgs3a9q{PN z`VT*CtZo0YVkX05oQuu++xiEPGN*^N{d!myzim0|KYUnmqHqoT=+S2lbp5bZm(F(DcDnKj4cJCBL)$I@a~y5Se#xBX#|+?(9`Jwqx->tJR0~|2{s-)U%@O zKPv@2u%|n_G)+QRl+R0lGVq7~EO=EgL&bMMs_tVH^hcAadogsKxBL4}&v!3I;{Zck zPUgX7(mNl%U>dR&Ltch4*yD|8=v)vF(_gY&Er!*2lF`f}(eUMoubfa*choDEoMMsP zwM*jny3?$IR7QVa5$zu1%uDbHU4xngw1@w0KPi^9Q1wY5?A?e#4n}Nsa{~Ui6Ijqo zsU^Nx5GoYdaD}<0DgQaBUuah2u%^2Ew)63DJf6rbvd={crl0e%e%iuU-WrB&RhYc3 zACqd)X*J#+*AI%6FBNkYmB|!FU(=kd?(Vl}Lc$MEn->a|SI*TUZj3sl3QnC4TK0-L zE;QwYWOpgN4WzYOfU-jx&4=F9?{-+gXtaCwY=}W!ZS5=$$-T~kL@sUyQwm=>v}Y54 z3dgt1-bL}^aKD|d8_YDMiaez)Smng&ip+o@6qG|=22QGNv|R$6tDpQ|7Iy{2kJSRd z3`mB7{C1E++kZ}kG9cwPoM%AlJbI+$5`%+LNa>^<5*e3&U2N(lLlS-aRT`gf2PsTN zk{>TSq*y8*;L^n)#i6Ae&!OwzQvQ+{r22xh-FRPzO?VsjPfbe9QlVuq-a8W#V8^Aee7E9b&hh*>$r$R3 z^=skYou0HBx9puRe`SjMSJ>xL*y*|IbhLN3|D?=2b@K6Ru4Ob`W=4)tazeUG&q7GT zz1z4zBCiFMZddxN6yTu(pa1!0vD9&?#s_ z5?p*)7J(JAVPOb`1W53zOd%Gsp->Ej1Tgrn%uy2Z0m_8tgdjUP;ToYWVz3oo7)g)H zj#+DgR_r4bZ$=DOq}~Dz*@iFP42`5wXMrZHqY-a|$Hc6)U@MC>Wos{Yqj+UCr5$|T zL5fkieDJEf=>V&{5+dF4%mZ?3GacmCrUXeL9P)sKMJW{|Zjc!yE?;?sN1TqK^dKVU zq@kUJAuOg}Uh>(g7eZsw<)xgRa3M6NnnpBStiG|HBdH^sS1&Q1T}HzcPe?W_HnQ{# zH?jOs-;-8k_G@E*X??n8e;nDaaa(HBm%BLc?Bd1UQNOq{b1zopTQra*0J(GnAbcOS z;Tt_oT#oi-HHAd1_jxOR&V%KD=Ds)U9J|L0qN(aF)*K2vcYvNui#KW1)tY7Bs=T{x zdz1Q>n%gUyXP8N&VX3%gubBtY*cLq8xg%_SpB;6MvfL}Myp1Pq(+fo|AjDtgcgMHY z7a_*Ny=ttK*o|7BNB5SoLfW=5_Rg{qOxZBz+~S3(evZCjsV~$w9rV)rmfV-VdmiXran@*BI4@q3_rTIRX0!wS zvb1xTUM6n+6NQlNuHolQO-4ATm>LB?TtKB*TfU|taLBD{gyoZUjZ9TT0l9^)q3SNI zY$VrAT>cG27gaY@$0frFE?*N1hpg~ivN2UX}n75cNQLLnZ@Fvg7hLH>K_i4s2J zqWWzlyCKyvjCTCWwJymsrFfDA&m+BWFS!?y+R2_!jl-CqJs*z;o;$i14x+gAe*hNZ`1%h#l)yu8YKN_Q@vAV9 zf9+k*s=rSb3ZuzTz?WZO$Ml=C>3p_D8^Lg21Q&;L+%F}V#d>y74f*#Oecc}rK%(!l*-ahydfC@R=U`h6mN%-U~LHgEaJjGOvh+pZas58 z?(HQ`$a|bq7$HApR>5m+jllf_z7U7&R^I#OrNexO@rFe}VR%Q^J4*g@Yn1Na^(>9@ z>%%JbWv9iHKkz!WDdyB}`kdPRtIw&Y4|D2X=hV|b{G8%6Cn>Xv3jD?A)sqKP^}Wuk zCx2jTb!S`6tJKws3jEdQ)q|zte&^L6(pI4|A9c23$D!&SrbK$Fc?@FW75yaw5e({`ygr{A&vIR=b&Y7$XKY z6zK3I@m7vFYk@JAgQwTx6Bo%T_p=wq=KB71M}+_g{g_t~7ob0q_Ic_|{N1^YE@L7F z#%RRGJ!HL&>y7H%UVuD6H-14lVJJYP@T=m+wYxF54)VjSN}pBtI4drVSrOoc(CccS zT>HP<`0jK1tPp3XxRU+F=SQ{w_*OIIy#FvmmbvcGko_K3oF|d>M$W9q)rYxti@Ei9 z^ER`q_jF!Lsa8#>w)wDZ{aGiaZMo||EO(1e$$tM{CuIL&mHcx~$m*_jne0C-lee0a z)yG@6nw9RYW+mtwyReM-n|Fo0YzPvsxu(z1ZFrDvnGK(d4(aHR6t$>YTo)Z{4o7?FU`puOmLLwx{l=zrv9Vbu?RQFg+fQ zU4Hko>R#u@U*_QD%g>UB!8~^OyUXV)tuYx}Ey^aCGx$p3;9I^U{}} z(hnx4hbjG)r}SUV$aGJW`oYljU}*YN3lVXPmtu|TMr&_4nc1(s#_dWnN)gokX?OdV zKc}btic$K3`|n~9vM^nd|Ivd8!OxPj=J0KGvo$~j8pMdWI4+{>{ZABeAve5z!r{DV zK1l2A%B43Lj8~p(@3D&=bLE{)2i{0qdAVTnb@Qq=^StW`VCMCB?)@PrWbUi!OE_El zYfjze+%1n%0Q$8WyBwZNh-8Y8#g@;Ui)SzrLAB+aQSwGUD1oT|>F1UI3VvSsg+;z- zTg|d)1r$-JV1M`X#&^&0Ue#zkb4H%mgL(UD-3hqGPVglpVAzeXF9S`LA+G?77yA?F z7hh7Gt^B3$*Ztwe*cZ`*enLg)m#?V_(OvTdJ!r@m%vazaLk*Iz`6~Ge{4@7fv>Bg6 zuZihY*j7ZT!bhd&wzAM-v}~9H-XfW_=nmE2zYxk5-9ZT;MYwDUghjI}h_aH#Ilw|u zv%+6f#N9O}M5OQ5=niYIRBVSxacf+MwO1;tL%KYSL@UtHmfRA?D#)SRF{kMjNWBYL zFV}`O64yXOO7dD5)gTX2#HV@%MoKa6?Uc^nSm}KEivRt*q9$4>lOA-?)YZqPfhMm( zUv!CiSU2vrZsdtI@t0PFA6AL`S|v8l4z26M-DG?y;U8ps*%33X?_X=1h`5<_FSdO+ zm-%(+1%5c?!N#?_S9ZSZe+Pf2_$Ro;%SY-pb<_T5_FjQ5<%c~RE8ntFX{J0D+p)Ba%mDVkdd z%LwE2Rm?c8y_}7o>oN`@o8BAZMfY+(`WV{Hd(WPY=0k7lcRK@r=+A-|T<+DgXG8b8 zXcC6QZpRxJ%y>mSBU>(OO~p-rnEb{9Xy{)|-I*t0eLEYMtdrJu`k*LClhi&JB#S}c zr9s-WLE25?@t=vQ zOgCs?^z~H-pHqSeO<1q47asMzz7K2r+xl?_r$gs`v;OAw*_$`@X6Lk7Kdm+E#Y&}8 za_paLqVOh!vvS4^=Pc79`f*;e!G%mnhrLtqD3H)#- z=>8T|oh8cFrc|YqIYnyYy zcGl1AEU2gMEVM8!-d_SB5_DBImeJuihbOhR>_uWfp|P-^{e1llEhCHfvp1ff54`1f zba#8X9fi%x?K>$mb}oZ$8;4rQG6o0pzukcaRrE5w>jJlr(vg;wWCKVpZRTOi2N8TZ^D z-F_IL@`A&FHk2@JuJNcRjm3k)d(|kzu9U+)JV~RJ&(}wcB*1p})7{fH^H)?M4JY*aSZajjYJ``2iE?^e2# z$r9SYv)bM5?Z1g*+vvZWn;YAIt8Q;scQ>m$n|nKd+ooFH>J>Ykx5sDuoldFnXfky#hOYB= zf8Xi(?!{;v%>1r1nFp6i?|k?&^?J_A+7I3F=*pW0{&=)fc;tl|MUi+hmHhmT)w^1Z9ISBdUC7Le85RG3@0fw{Gr3UlW+ zFn708VLpw(%mH-%1qeF-5)_?(36jolNz*7-_otp$OuU-OuO}ZL^oOtfv9AAO2=8b8 zuWoGa?8esrt?I-2e;=RN`oAtVe7NGqU;di*GDv$FZYBRJ8b;VmFq`_Li&zWuk$+us z{_p>^CM}N!{%C#;0L`bh)6>Ix?CHtG8@+BF#GW5}Guz|;J^o;O);@_neOGIIe@3(r zw(@s1*jcuIY<12WC!Kdr6(}(XyrkC~f>06j!mm=$M0GFmf(jT*5LxjfrQ~@wDOc69 zayk2_FbOn{8|_Z3@y~jEInx%vUv+IvLR)cH&}Q-bueTyr>r7hp2QZhoTFLo_6Xe7^ zw4;s^zn&X+ljZRB_2Dz7&QWW>^S<6J5!g44!}_Yz_Xi$#Yc`zVk4{kh7XJmn#5)e& z-_C*nO0lS3q6tFhl(B|M>Gk$#mJkr<&Eyr77aHUfsfP_dtP;J!#T_VeLE z6~x3RQ}2^M>gi9&7fAYby%Y{XThE=xkNuM7K#v}k-Af==|H7MfbpTO%K`9_s{p5|+ zbo8D3o%FN^c%sPXNH3vsS#L<9+8vAoPqrOpx*$nr$Z#R6VZ0CyaDtI1^$7+Gtz^V?PH-mcn;Rmn%YU&gLM#FwN(9NA;VJx3&zaTcFiNImri zo*NiD0~=&JOg->KGKy;K7&YgmDdlMAfiEH(6Whim#U9?Z#nPF&pYSBO;e2uf$2}j< zXU=Tw1g<}D+)wTeOfScE{yiUkEU&0ZV}4eCxb#M_{qOa>(cs2uIG3I~?Io6OUwTe3 zo{xIYxQ`LZhZ9gV92b5E@CCn*N6riiox7utYio&c8Z$!qW<00L@b44|<%4_;po2pX zyK(OKV0fIq3)00K3@WKeoq8@vh&O}rCzz6l?nlq@fg*s)cphL2eHXqC0}$NaB$*hm zfq*^`*BxPC4?tuBjhCGh=LZ+Mb~qoU(9xe=QqOzd1ik@zs8fUSl}EyG=v8cihhYTS z-BpAyg5ME7(Orha+aC;s9n#e;kIn6}dAFmw`Q*7_<{9{1FW^f|!2GbWu5OgAJ(5a| zcGLmiNUd48g7X;EBx@zxJrOMZ*}z)(k!t<6_!{dKGzVf$E)(r|nKs3;?)d|fIYeqh zU(7VeR&gbYsabN?%?Cei{8Exmud$=0sQ+(KT$q80EZkiM5dRl#p%Xr>>Xl`EJ8T!J zIy020o9YvM6^g_huD_x*6&LyqleIK-2ZM39s2WDi%+$xJlDX*jdtvVg4lchVcjysw zuB?OBwLY9wKG0`Hv`b^rLo`1IGY`bJtQ6M?fE+OX{hs!Q=zUe}_sD}7)CIt!#66FY z`lVD|N}wltAE^pJYt`%Dck0Indg<8vR=usgpL%YOUP~X=jH7qS3|v+#EOZpTKl2f6 z)jmdP@LBAPOp=Qk5mrv~)mDXmGbU{sqbW9D+mw1$&S*aTeH;iTpVpv54Exk9;S9#;yx|}Ad+k1Zp&1TAIcb%z=w|++TT2`P7Q3K! zdjFYwqweqISo-%v*%;-Fx8{ASVBQ7&JTUem<9a?C4 zh+UBavR-pZX{HFnpR&mUZ%h#;F;XO5m=bLzmQQs10xT%{YH}P0RU&jR`?GX!^UvMA zt*_g?{gdN%6tLmLNAuebXtXxcR*A?|^>(+mSk9GeCqu{wie{``N7my79kDV>}9@+nG zK=FtD-#vUD_J7g+pJM*9bM6Ly(mCU*h$)i1s)d`z z9G#TtJh=rrO#NJS@j_wFUaOmg)C>>yvU@Gj$%na#&H}y0D?vFi;_LY2ky_dAXO|b( z+PQ*4g|m%Zce>*_9j)x<+E|9CyQ=_UGRK-cHCab`9xKJ)y)i1IT;s8-fjXUC`I$QK zk5!FR!E4-Z=-b3vmvn9t=X>Pil%yq2ycWU55FJ2;dl_KR5Q3rOM4`Jbd^1u>XvKmNi;X_p)W{v z3JR4$_gbq}Z;GO%)Pt&zgmz&w5BgUydqLZPSAyOmXq0XeP`XL0-Z`wlt?mEVIj$Ym zJGB-=EiH!h!O7X{!}@Zu?s;9pTKTPK;k?MWs0?#oVqBOS+{Gu7%lowQ{!YfgiXjsG z&h$>0te*?^U;OQ}eVZ5BxN$p>br_;{WO^~f5`G?y;2bqybwDe=?C2MqwW!Hq`UTw4 z7*8hdG$8GL!`gbb`Ae^KSL>IJN@+XVsj~{-mT5IhFqFl+shP%7UT)c5hUPE(ON+tF zQcyScq%;!EPmpECvXl-DSN4|{Q`O@5hUTa|jillmnw%^zBSt1miHKP#_aM!x1Da>dFA?M{=^uG4_ z*&qEAJpgb(kH7yFr()~NYy(eu_jY!Y{D0}YdH%b#Q{DWVv$ISjl0JX&^WUWL7tfc% z2aAvYPIYVVVf^polQsTqZm(S4rHw5A|IOWDhSVBd|mNT=gDs5%~$#p3(p$<5TixSTn~{gSh}vGHUL{ycTwoHfth z)mrZwPRs4N7z|ldfX{)C_OkQ&3{;i*sE66HEFv9J0x|6{? z!2b)Ne|gi9v$DSeI-m@WQCMFka{U8JUjWG`(NnJj9u;>mxWSPDQt}I`1SHYx(6isD zV+AS;7Tkvs{ z%kOSm%5Nu^x6%j7FKz*|+PPxT?dH1mRT%YPJn&{7ajh_Jt!5gxgb|ifk^&r7J$-vr-CeS*c@T!7kMb? zl82=^OFJ}AqJ#dQxjQJDu^$AK>#}2geuL0Kzj6NYnN$*+Ql##NYm+0o5y8q|TWS6i zeX{m{DyH8Z+Q``dZEx@FMfZPuI}iK6d-){o{{nwFfwe}uQ{s=IH$rptc=XrW1-68D zZG0OTzA<-w1OMENff>es44=6{;7w;s+8)Z-XDxlyY@5Q4_A0VBjI5UuyD z&K0_9iUJgRMqe-mnCGZ*yL2N2qojC_u^vj}J{@_*4w~t*m%N1G4Fhi`Zs={OWayDP zB9%ps7UiKtYNP7P86UFQfXC|F4#g1z2~*toi#1&8a`c|+-TOvpr*_QmbBsF~A0R%8zh4qT6ten5P9tt4Xm@K-LrLG5K7u9AopT6 zoP>vO0{4*!A*KT>IsYB=O3~X|bvB)6$%Kx1*N+5J8itRf7-}d1njMK2{|gO9+}it2 zF)a2%l(YF}OF+@K<9;_5cO26dM}krfTMuaAE%}`DeA92ejv_vBII>VWoF2|v^|=m8 z4>rhQxK>N%7;1weXz<+d65@k+4bEfZI(8&ll}@9VOe9^XY*-`&Plo8!kjK8pbcCZweZJGiLutT&%AH4+{x7a+ zyX`h0L;uI)K2!hS+}PUQdC>pw;}czt-`AV1#>sK#sP;c6WES3yE!e6^wQ|2i}!8D93tHdY-1d zMl&i{;g@1Yl31}QJhUp@87ad~kb#l8jWCl+ba51`TJu4>7zJpUKi{!{a;WTW2!hdr?NUFX~)Yq5BKt z`K;uuIkEX5Aa%vuBZ!Y3qrQ9P`U3=DrrlIVR#6wNyUu^d=JPWFo6Mat8<{WwO3Px1 zo@rTxk)ZNwAX4YDCRc=IO&J)igh3Ce;_I84*9X#JJCBJDB})}W3#>A4jDYA&hc#Ix zha|cCny~P6zN=wAPB(v~QSDr&_OK9(3lgnD>nq#P?WJEKBZVU@W}#&WUZgI{QNy`S z5{bD+Q_E&(4^7w!CUniP9j;T+HR_+8q3>#ka0COx3xNtnkl%4#{hX$ z$3{ekN$koS*fT&!^eS`{|DWg^S+5;ol8I-wzJ(L=d9>!q_s^n_U_d+dX7i-^Y*~XC zE*bG*#82d?1PI{H2QzC|Oi*&5;v;hx?cF^4)QJ~s=eCM{Q*fO@9y5#L;Tl!`6StY z*(;R?-*J2HWHz2AnSHmM_XD@*5B;h0emofWK92v;M&CDyM&CCH4qtrD=L`9E$z*f- zh~zZdCadU?oM(%v=(p0g<%(Rz!hLR6Y?ntAXq+6O9aig0xa-=0eHJQ@caxF*yUWNw z^CwIG6Swd*xb+&4b^gD*8@>O#x3j(ZAph^<6PN!Si9x&c>0oL<)cVr=e*b)De|!z1 z4tvA8WhpSB79^boT7;J|`$+sPd;D|y|9nyXZ+9b>|7~yY!Tz^==LvNnBr4Ee3M(di{GlU*--?|*-!j=#+9$oG4J2w>4H`K(V|tR z-j%Q8rU|^Lhu}&f?&95U7OzUqqpL((Hj2=XvICfvK~sdXdR2H;a#kuU=0krJe~4A% z&5K4Xjat3l%63n;_+#naR$5poRZ{hf$n-@%S@K`x2a+nlt=55b`M<+uM~AK!98ZF7dL{#*>4An_k7ObO9E*5 z`nKTNPb&eqaN@cXXi!VL10`ycixeprw!Hwq%W6-AII(v{+%BdIKjt|)CR7j+5=Mc40SIz(5NqsJ31#R`I&`W2^Ye6!Fp455O$j*B zP~aaM0)ge>Z0h<0+w-wFE=P$MZ$~hspwFs9|60J%bIp?{6^!Ez&%GZ1_(^IH^`qDI z1JG7TXKDPiE<%`#7!T?2G9e+cqtr$zMS5G=Avcv$F`*GT600z@*XoF5cD?qh8)(sJ z_FJ8I_1Zzb`8+H@R1c3h_}O?8J`q%>7t{-QML#qWJ&zQT&QnSKH3%10!&umyP$WU7 z7CX3@mx|v~9b~SOurj8RPB_9WMm@h@jP~c7o7T>E$`av!XlrqIn&rIl8`EEv;4k_StBE+XS6xLdg-j<8zzi) za!)YTq!qzo$X~;FRucE@5XkfN+Wc_hB?B=oDspi)s*lvspn4q_S8^_BvdbduWT|eD zP=qI(oO@xRJfcok)Fz@fZBxXcQBf0&+SE)C>#HQ!p?)|I6B9+n)}|c&>c&a0LZ7Qe z6q6TrIM_kw${ozTB64%d`B^{1P7x(kx`?=As?_s;m!4}?VfJI~7v%ioT#RR9r_+Za z_Il5q&ssTt!+8{Sy!!q%$i@&Y%*=jqJKUh+7eMgJo%-&%K{^UM4?6s09PpLZTcI1) z!B0_>6D0<3Jd$lKr-z|}b>aFWF$gp@L4H9)^tBV|nw-b#rH%y6U8ws!)}RKwL(L^O z-tqgLU%lx#fvq4gMNqn{-s6~24d1=5wsQFxSgJnoQKEWRPY4pkRAAU)+t4B z0ipFObxDjc12a)1p!V^}QT>RWtDZ;7n`0in`-}#eH;oTRb&ylHVp7?uZ+V4;p3oY3 zEB=FWoVl!1QUk+Ej0ya?#;GO*tz36TQn~Xy;kC%>$L;Bb9zVu~BR1TZo(o&{=tv*e zKeRy^EKc+hEf)24qO>}3zI8UPtNqYM7TpC2XssU_?frNBu=Be9Rs;bQu6wI^``%i% z_@;SsRCd}YC3dsJpH8JxfhW)Gzz^zgYG;QSGf?@%SLv)AT-nkh=g()>P~2f)Od~jjwfEoljsVqIS=;6(U1=! zxn2@7AaEXQDB|Deu+|eoQ?y3=3l`8``4rSFjg5qu|=~(x!23i zYH`lt1YxbPWRZz9>`}vg(eSZ%;V#~=YdO3GCxG1wyvFy3Q&m!7C{8C`m+3f{R(@jg9vs(Cx+3J${zFsTL4xgf{k zOfA{vlE$7XgKRxZl?(!UmMRIPlvVncD!bchReC~(^nqz#MN0vonI*o2YycdX+JcA}v3}@VA>+$;8twW~Hd0RrsV5;)NA4saO2C165jv`!cD}FwXcHW^ z-I=xh+p$(UTSd2gu>z=Fje7CjS;6c`+?*4iB99+W;s~%>KA2rj$McIz6KLXOLraIe zmNmgkra<%;Y^qA!j>->O(-)_Gby(G5mIq4-tJM%eCVDCSifNZcDIWXh}l2=guGpAzOA=AC#_E7^xesE-JVz$7<7yt76bik z9i-8)(ABv&;E;r8^!fe<|rt zHSwl{^~roNShwqh_@RE$)n@vFlIM;1oE?^MjoaEhJ9Pd}7I^iF9)wZhlK{sDoJV4j z5kG4jw>wYlYh@lg%gDlz);6lU&+J@~5#@gJyQVe(2Nq7X8@WzI{IyV%?~vE)V0T z^6kQGdrPjpBh#kx?7}R2OOCxG!>01O$7$9cu$ie`(ig)V1w%61(fb{KU6pPo5I`=)cE~ zYJAJ#qB58@yR*n6_pTIwE%yDLc69md`#bIElG)c_lK66<_$6hN5%G<~DrMp1Iuia{ zS!;kXf)*^~U@m~gIo_~a91BHW3l@^f3t$0NELddnA6tg+U4SN;vuQ@}TRZipAA zLhMoM(c70e8!<;hU_@S%Ar8X()bN9ag=XZFYo#iNe&nHx=UVlbur+zku=c7ui$xE# zPKH?Qy|im8G_^YZ%o{2fb7*RO{_nCA^)nUyN%l33mRwsytl1Ovr5T-4e%Sw%TZ~h9 zZOyo3AV2G?l!dfkT;Y2K6QJ;a^?n|$l<|H->A8iMHILTFsKra^uBguw=`DEEBsO$J zTuOsp#kxdesNZs8D75H3zY`*sbu(k!5l!o;hh6w9azMfm!9ZsZkSdTA19JfhWS2Zr z#^~)v7%=4)o%|T*te-_Z(+OT0o9Hfw;VG#&%N24fY%p>a&KN+W5$?Noh#HRH+M2^) zVW?sJfZepQV-&nm@43W)2=dGVXBF19TH~;O;H<7@!+~Bsi(t1A?YUMQzG{(H)on%n zmXvWtF>=XDyQ*k#GhPeR<4{kWHF@d8as^D+Ud8P>x@W|Ufq4<<$#=`GufqG`*4OfW zxAmpGF>QTG_nzgum~*S~LNjw4&rNk+zKU(s5?{z2oGcJgI23lm=|}yx_>f!PSW8Ra z4BI<&o%q^u7{1mgGyc=myK@N8jQCG`TiaXF_;1@g5Ai?l<&zZu38&{8h0-{GNCEx6 zSx*iMYP{6qBKBSY*wd4V zH+tPVh&?~{X12%wd;G!ntbG!D`mWaa{tTY#9lZ(G=NuF@k~hj9UwK`%BGIqs;&N3a zvlN$Pm6W1yJFD=hgSS6CNmW9lD(uD`1GI)B61cTH8&83;n;!i1 zMqT921w*zPsE4pS-OKstV`3kp1ydm80O5~d85Y=?8Ne|t0n?wa#ygXl2+fhy;HrZf zv7Y5Cp`kpOMS_ITSgtxVrO`!RkeXLNAY)jIl zi4gc^&(UNlgC5k;h%}bs`|r+kL*m8UT ztWoKV2>e>B(QHN0hLf|Z=_M_IG5t_3TvIXyXyA<0JlRMZLHI%_cTBToW=;^3!O{#o zk7QXeFoy40nb=HVfJ`>LtWbm@M0Wy%nA1$EZ5tas0{54kPLV7?(eCPrfV6iBslCDydUh{{lPkMdRJt+6L;N+wW3hX# z&KF4Xb#~o=Ox<$K!&DuK0qNC%+9J}!b1eFT z8d-ZKN7a(QBtrx-SIA)YGE$Sgpr0ES3Kl!4q%=HB5#(mfCw#Q_O6W&fS$P%~@(I~_ zxX~G3=g=@PG#$+G_lqv4%00A$#IwzoH`(fF^`jfeRE_wsp&|C(g< z5vM?j833a1bLIfJOFnRwY9_zcBHF8He5F{oq6PF#rrMu-qxn!QZL#w#NBg{VIE;&o zK|(dw*R?uM_ZwW_{3V!()QDM+jMs^-nJV9$=3j51VAo#I=nfU^*$W!o zp@N>h;O=&`U_Vwch9VWLZdeP(DPFBw3&ts4-Lw`oP~?JJmZ1^110p5kEGe&XL<+`P zQr@?S6x>ZJ_#{#=&XMW{gIVxNyf<{yAWrh9Wf30XiLHW#^scNRq7B5nj$<{>WepK^ zz~WUHsS)Q@>7W-W80S^?U5num=T-Iwixupbqmq(6LEjSNXu&z+1!yYkMeJ_Ru~(s^ zaO{=x{nYH4>J%E(g6A0vgYN!Rj=mBO9Mzn4$a`EO(o@Yg!;r9TNdzYIm+5GR_Jzrr3k=CheU z^nlrhi5r3?oJERbsMqvQU^{bEhmFoZKJ$@dA~5{pw7jYB4*Xy3Cz$%trCbBhCRkk6 zfk=yBX_1qQgtI_~ryUG4IP;iKZ`6^VRQh=w7YNTs3`47^ZJcNEvsGvEvFzxbNNrI& zfQiD1)I;m|B4AU{Hf+No+Qwm!NzM+AfwiZUHM^Hql|A8-A!wd*1Sk>C<#=kRHoZx$ z3lFR{v0z%g!FY6Gt%xr&Yi3l>gdx2MlWAx88RmnPOhyGPo1O|<5@C(#fw>?h(n^8K zPp@?1n*bHmWUzQc*A9vA?xrI}*Gdwr(;bzp`dgcc_33s;R{h=W#QGHfAY34Fs^eeuePV|r`vXrDlZkCbANPOWbg{| z5~+>gmQHQhL#1|42|3(yq#%bEotM~G1b3^xM)6IS5^~3x0$sjQWmj2LrF$sRAKv6z zVasPCZ4W^-e2Ain1I4P2UIv^`iqi)2^)`#M3I1^&J#w&EHndI`RoUf&gcT0q%+?@Md1)Yh>mpYT4@%&a4!#7rDv`q!00!RDL9 zC@*8nEL=n;9~njGrMA7thbl4VOMPRnuv+RqJK~rWc1U+g5>HEAc0sH1Jk`9=xIpNE zR{Uo2F?7aakFbbGmQGI6A=qFN?*Fwxo*4aK#3CTNN3Tf{2*E6I87xR2D4&yWX%}DB zQct^P=`4pndRXooaW7lDPbV*gMtrHvbsvG93wU@bGpS3rUu&|ugzM=tx}+A@yj@z- zl9JUwTu`ZhWw`|1rI%`1HGAnSdb)#u9z4Dl{0QP@_55e9NiU1~1Yk&nGHU-AcH{W09)-POEvB&|p58 z*{_XWo*(2@wq&hiXZ zmkQo$n2ar4_83`^jzZM#W2KMF8)!OZP-LfUSch#aNPK&DL&_&k$7a1%Z@#Z*Ohvtv zQ{39LPs1j5^9uTo*f;^S?SbSW-39hVuu{7NA--sY&jJtL`A~d5>SBxpXEsJXWnin} z;4Teo#kwx+I`OeCrxfpWUHI+KZXy?O!rG2I>ZJmJdh?;zOJp+hA`l6IXa;ToGi913 z_QDhh5l(yS%>rjKCQ>2skZ$)%`z-90DEVEDg}8{Ho0HTu(#cM#+y#aIcr?E+Qb+E; zEjsd1${&wse%}WPgA_BQ>g>{;S?P!b_Ikg+0k{R}v!;n{05}rJEr!!Bcr@KZ(mrrN zadgDyjh6w`%>%kIf+j8>s?WP!*fD!S#W?|5Pe1uE6m-Dqz;LiKF9zu!pqsiwbfx63)|0L-a8SanzsDL_YaB^@qLBKn+YVrQFQ{3nG<$*=g=Pz60CuP5`c=3 zKe`$Zt^@&=LcSr5o<=%+E7x!Xw_g6MWfaAY10x|gxh5yDh)IgMP-31yi} zC1*U1%f5sSz2ONfMU>jOCyT*Y7j63=HE>JZY4wT=AeBIO8(vLs;JJZV4u{@wJiQ_7 z6DTC(VJ2rxxpt4MNTxtt#^?jSHTM@}DX{j)v?av4CR(J|sr}11OjrEnU3=a6EN$nL zEG*ZK#%ipb+n)PBAkl+VC@y@1>dM(NLKmDnt@)!2;rmw zdZ~qS(mMIAEX2Y)-d7gI(i_MU+Giii1*(re`Gyx^fNdJh2A< z5sx)@k1*EH5?nh-$R33|;9H)!^NQw=#I9Zord(V_qp^M;x1^thM_oHiK*^VL&2oHy z#Nt#E7O}O$5z^JMj z!d%3FLZGxm_l41j+pH80afLb3t_Y8sLY2f=##Tt3(YOQba52gOGHtI-AVsPX!Cy9r zO3zG^UHSzBJ3-7QgHPJsG;T<{48RZ0a~L6*;ew7goXqkwNE{zC{;pZyxHV&&Otr(t z+hhAYqE~VULL@4s%%$YvW@E(SgS`X>D*TpDR+1oFb-I}1=UaTd_R5fxVmlOlQ=#4d z3u&Q=?r2ho5u&R0a(qc;HpROs1K3PUZ1IO_Rgh1yaA<=tu61B#>t^_NbK9UJksg$= zm#V)-z5-M(~1FkvMl2O0peCz!Gbm&=?O2&fY_WliO zyD)M%Og(H`#*>xZTeBh=Uc|98!>gBO13FvSYpg|TJWnn!2Rxy?7<|zIeLMY|Ap}rx?25oWb& zRU8HO-B5~IHLoO97`{TyzRS^+L$OEC`2~poEJ0_dIWrXYv8?YHgxG`PfGfPj_TiuB z;Zx%tA3c8jID(>X7|$>PF0g=GC{Ed-UplMeM&T+Vwt6mpoMRF=%%yq@a}^s({&2>s zEc=fZwCdJvWY~W;cXs!p`M)qE+=Kn+9zIF-pEitp3&%ad+*5txe1CF$aN@MxkM3+7 zT>i1mJnhC&z16NAomy-?`aA6|kDMX$p3fq8dJ;D6YO)FWEW?(}t~!AyBx;!S`KmLb zr=R2X(^ogfxHB7T0RqgEB6(g+NoQlwSDjfPmk`t%pcDvm=n|eUNUEK@{-64O8*lEY z#0%u&_1vs><+~N2QAJi?tJ;clp5;gtT{ok=yW3$N&{+T&F@enVp{8=9h;gj zVgiZb8%qxyXU5OBQ4h3r^mnBrWUc@6nLqf#=RX@eJDaidA5aM%*8h9>B;NmparbAw z7bLC!+kdu`p0jr2(01LIA6qUHowe!*MYrV8hOY$Q^m%Vq|6NUagOe{Fj}rjokw6vn@?84b-1zXh?@v_z z2h;BQvfE%M&z;2mKYlmm|K{!v$p4*XBVzmf#pST^=-gLZ1>#MjEl-~*XkFOu(UPLppd0I0F)tjB8+6N;N)YF)6s-Byj+)srI zX?m$u2X7d&m3p1M4xCD*f?fLjJmOptO>G{39!+VVKYP23@ZR^QQRh;Ts@mep5buoi zvc*yyTeL4cemKCWW}}xc6s6xc>u&^!;Tw@zKalYI#SrZnJpGjoV(6Z#!{jvLMqS-A zHY^gN8}GrZEM9r+RGt5h*L#+yUg!@}jND3!62AoHwQ+S`7{6lPBgW|n{ljCAr%aKS zhyf7b-sbvdN$U!J_BNgM=!Gd!;hFP9pwAA;S_Ee1sgiJnGXUCQtqSx)PcQ*9emyF& zgFKX!vm9{I_?cgLBaBlbUH~!mxxgi4H>7vEVmy1kdod~U^fF^GpBpp4)@gDzmKoU7 zD1m^3 z77FYz_wKnl?p?ztusE_^8YKDvgpqc-9c2XkoKHPhBdPBCM{Q{{fvUBaYR-@m~rt1A|x!e9=nvx>*6Z49-02sid_N-A-Go6XL!NV9(L$TIxvOrl>W-_^0~Dbj5C6TfB605$6*Q6Cw1d_HqYTk zV>wK>6d54mL4@j_n!qnOOGus-BC;IHy9B?c3xR$SO2k?r0Ke+=jxbA3l{EMjHAncH zgAoLkqK4)WO^gdCiO2jik0>>qEvZ|uV=it|6YDfaRp};4+e&Y?h$njd{8KG64 zhc{i+SSvgpi-#8R`i&v%oo~OzMqe7#Gji1wwaRPNQaJaqbGCO8j!Y<`P>c+j@F0vS zIT7!9`)Wi&z!~avN?XT<{>9YQqCmyuiKWu2GaE;C-zhUSdD7EGw#BxImN^;WbHPOf z`-^OD)FcFyMJtIrIU74I^H4vJOg{?thq}p0@ z@M{#C7=)?!9|3|UIO!-|i2jX% zWXd|lu;>d>)&>_p5};^44h}z&EsOkojy;jkRQqNnO|Vnx1+_K&D1zqI*O(-!Ivajy zQ$mbiig(nqMp?R}P>ZQ{@@=|uCrd@tyC~ynt%@Z_Rx0PxXOW-v%|$p3U+StPev-g&pS0I=DZ!C^lVtR@N2gnS>kfnB-44^L7oA_QW>&L2MlP*^z1F44kD#$q3A zt%uJI1f$H&#e^~|zuGX=3cgeYEWYsRL*kJO226-=fK_mX z_%VTsu#mk0*6@`!lmzpCj;qST-k)=1Qinl=lE`c5H<;;bn)vO6Av69LV%TL)Y94a}$Y|Kdr*OEBOnoEFg=g=W&u~e7^ z$RZitaB3S(f&hPvEvDRBV&#`)a)1sZV2%R;~=Ifo>h-=+biu~uKaJIC3M&fIjU5dr$=&Lv$ zGJ=I;Oh^%7!-+qY+hrB(#3mnMyl;(A-k2bS(0E#WPH(<>8DjU`SzCi=hMja3V~3E5 zl%t!dpGF9f^mXvIJ+hLcFIy!ctZ+{Z9n5Ra7ec{RCWDZOh{wIfvAK_+G87VpsgY_5 zmAt>^=%b>`YRw9S%PRwjIA0kS#;AxdL_39R3ein?T zw`85-w(~^vB3z{6`Uf6hMb$PQLU6X%)Sx=)`siF=WayVwVy`?gEiO1;VjmG}UT3Bfp96yDdm+z;n%7t1gmAD(h@A zyK0ca9(dF;pO{2Z371;p9?2?}_%LFo>|4S=iiuIIv8@d`Sx78afDemDHI8a|K7c zFms0}7x{X#)i^nhJ4|ua&d~ig>^rgnbdGBObJ9!(y?&Xto{?oe|xkK1V&u%7o%rYI%XFla2 z_*0+#Xi+o1uQ!kDhn=I7gLFhV#omTsxanflVA2yEb_*z`_vcHy5 z!4|qCE`@P;#?-~IBqGqDb{6H2lkcDDJvwUbqdY6oQ~3*{9ao=hY-sgCR$@Z|LB+kX zy2f_eY|MAJot4J!_IyQu>Wino`Sgjif={^n0#rgXl0w-d5R4|!zgb_Sx_snx!j}S~ zF8JvWlf0-uLmS8BV>DZyqt*nDK3MEgkU}HkU?kY#apN!+IL>H%6&8`y+$CV9G+G?j-`4U&j{^78fxkICskOH@(_!`pV|PX{i}ba`p!+#M?{24q z?v3Z?15d*@18A$Q_1_#v9PvNX(ExY%e7{+_Tz$`=IwTf0R7G!+nA+Q>$vgGB!WdU;>TPK zR-G~0HZ^Qweqy#typbBC;Wbve%A# z8KYqevoPh7h`fYo0@k(b6 zQC~5J>|YXFK-0h%1Opl4s;rn4<2Sq-@R?Q7 zZ|u6LJY_Ttd}3&E!5|g1_@;4K2dOci>G|EARg$9?M+x!BSLd@%(RVO(wc`u#1sq~u zA7PQP<0t{d9M4^XZOGI|H}{r@`Fdjlp} zNghN1C9Wt`cx-M$#z~uyyP!(Dl;|{hk~B+-upfBlh`I8_St+T(({?B3oUsDvVMPq% zU$WFT`0uXNQ~Q~5(thSH)QfC*Oi(}RkU-rTM|J?H(8bi&=&k}iP{wIn5&Je|!n)=- zwhf6Qkp@hs=ISJ($31>LF(0s=Ispkb5lZUBdLzxC5&UP-4;Mf~*ZAyB{IbY^8ru{T z&TrP?lzMxP!KA%FIG)xWMB?tj7|?L@FizP#BvWx>+XR@sefcUnFsb0;dj8kp4{Oiz z^zIft%x1mMNvs#!maMNkZyFzt>I-Oek6xS|e}8=P!?E*UzN6GTXXO>iWR#{$SM`i0 zBYI!hY<^Y5?80EKqn@o6Y8yCtrVxq2xBsrb|9$hN&jg(Jw^*l`TBXx@UxT&aZL8BMJ&$sgfW=Ym=;rc<^MC(GuwT98MKA)e6z*>A zu)^{fR4n}Qm^aWlKT%lH6({;+jd0C^75d{wb4!H_vjAJxFQPLf9DZBGzhLopR8BEC=`(^Cuu||{%uk7YCaSvNZj8`2W^ks|u zOlHe!TH$2Ai(O2Q^nUq7UesVA#RTO|#i?90dk~%3F%K1et;s(vPQ5!?e3m}BQtU)uXgX)wm%`cnMJ-Ls8q3>Sr@qWkCfKw-XIQnC zj!dd8?2B<$stxVde6_eWZ3%r@iUn>7i|>lL!FGzKjn1Z}ETQ+6)xfkxg*HLMWEflg zzeKMWGAu5;@nm=z`g}$+dCi#jP0TFQ=iTwSg}HQTjpjG8{;~Kd+n{^jd=xPP-@W-i zpy4?8m@hEiOEWp&eMIbL$b0Fu&B}Q94&B};e)qQjaEA0RG0s*)_1$S3H*9|)?1hdS6D&z5pZi73*ewho z%G97BsV7<}2mIjC$Z%4;)-42Otq%P(ftD!zN#4!9Ipkuqtc|&Rl7^Ssa^ILgO`w^0 z85bE0yyWI9#7jMi;1!mq`DK)+Cu#jqTfQ6fr-?*nKO*}yn(gt8)lKvkg8e3y7S#bi z%IX=184Y@)0SR*oX)rRjb=RQ0>TTrR*Il#T8>tGKZxrElXcLTUb(7Y9Z){QWFq(r^ zQm;;~ano}XJEu&ix1w3>322$u@pAAq6214ch!_V1eZka;Xhilg%DGEYjG%98Zp@nu zw_+>>Z<%bx8>*1A7Ti%8tQBslOoTImX`E$ke=)wAT$8E}+U^bO?C5imToe>yE{dA* zABq)|Za5fWXDC95?f$<=HNCV5(CPR8w>Nj=_y4yxs}J}8@8gqn|DSo_FLd>Pk$mg? zuEV?1gd)+z@LJSp+|uF(rXwYfI~x!EZl^n*k7lsb!JuK@G_0CcPB^+1P-jB8BEU4u zDaC((jiVXH}i&*fjje@ zM~@tQod|+l=V;HVLqT`4+#oP2<2igW@Q}6D+e|QGuV*d*iKUS;I+Y44)OXnO7zPGo z>P3bkTEJ4nxKyTIQwXzYVOV2l*QKyZWb3MmVRV^WXRZ1{aV{3gvDi{r zHr~&5o5R==HPoPp!VH5KJc*!IcIYYey%)8;t%m+rNhq$pvf~)tn5*JyJMQ$1PHZgH ziR!Mk7uDTARyVdbt=-t#{9|=vciY;H-R(b4H>?xm_VWS*o}d%7v5{G@U$MRwB~eRS z{Au&Lur&sDV!KgWSFBxMr>U*A{M5C~*gH(v2l__oYY`=PkR%WEjnda5Wd@3d}+2uM-X`SJ2`NAv&EaK}JiQN)*qZd6Ilj}`^~`eJyA zBPfH6>flK;L@kUq2OaxLcxyJXg%RgCMKJ|Vo`PbfQA$U&`%8?#`nq%I&Atm@FLmWj z-3yN_C4Cq7N|){|8gZEcmK;BDTnDWqXcd|HLytFU?r0{V&c?7~^MrK=(ZUmC5E7q) ziY#(Gp5cirSsk4b!XzM57f;?1z}Dv4xevQ`L}@Y|_i$r7?nBYl1iU2VA=!&YFK z^W?bR*?)I-{Cx)rVR}y`g3U<|S=6}E1S5Gi3g%M}c*FxHn981RIKkz3KIq}_!j$r+ z(=oaZjC$C`U|fdsF6OQjEyoEca?C{LNy+HD2tZCfV>3gj55$2*BWS1fR`l|AlfUD` zi%ikgO}?FF5oSgd%+A~6v;9t|gc$|GctoAXTOoxE?m5jym&St55Z64zaX^6yb5 zM{t^50ydxg!N73`pWK@O;m}h0WyCp>bomnfsCH_|^Y9=ww=_q@{5NUCUUwM`qdaDc;{eX-|4{D|CifMs|xdAE%v#s_{FzS|7n>qcb1%zlEg;S0SG zzrgTe?Bn|%zCp~5%1+$VTC-KxiTn9a#R2sXZDXWEKTT(K5_=u>7K`td$6z!2b;LJ0hO?G>Jt zuf0O%tAq$^EuNemx9d$BDwVJ>$z20Y63J;usgy|b$R6*KRafCDbOg5miJ(_YDz-;8 zIV*K{n$iqtgA5fpZEbBexgTL;JWn|9lz{eJO@k0Xa5L(5sCjqP>-0Tzl3$=xrPzct z8ced63)<%F^y9}lddzK^{h}6b*DuRRC024Syfh{e^JWoViD%8@*#%{c%z5(Zs)Lh4 zxqDz4-yIm&Gqi{^t45F+Mj2*Pn8?ji-+5uoC?lGZrSwaqwB!T&N6iM2w^-~uY&fg^ zN66IqGt$q{KUM%2(?2SAw3K*MJPtn2&8dyzD6GA*#`57;A}?#4IQ&{9R<==eO@p9U z8Tb1s04MI0Tino}+DFXtRIh|-8~(8gNCb~fzgS!1IhIZtS0mG7 z`k=;nAw)z+EKyi>ctD9%EgQy zF&>y2Sjt9yG#+b|DNzz?BBE4?^@bEkzAz9%b#zR^?<)OuNI9oy z7Js3Yx80J^0ml$}9xEQ@ijaDU7j%JB|QS2K-iJ8oU z%P_sNRAe4F8N^= zPSW2$+jUhv7OAFY@ZErPtwzjbZZIM%e3jEFcHzLHV3!SQn-V+hX<2IOeBnSaTiEMY zbQ3`vI%CW7Sl4;h9EGWInnWQ_HJv$fYb4a)@VZ4hbtIvU8yfa4N@6~D)!x(_cy8d) z>jgFttIl{3O7&m0vrBp|B~48KwJO>D0ZwGbAUg4rG-%sSFYjhdry`)6qJdAn02J5- zY18QfNvGQGDpBdyJDv(YhlE_dbWBFZ2$Q`#_Lf%0 zZ^aE>`4)awLS0EC(erqbM0+)%Xj9Fx|1c!RViRn_R4FeA(G`+bLn0i1Fhiy&JKxGASydHXw^m z>!nTBh}M=qWAjcg-91zJ!5kg{CP$}@!+Nvx`mDuXG@Tz#n%~!&Cuhe8rGi`zo;e5p zbavxh0wvH%)}>95>zvO6;W^uN$-r}dHqzri?QZPu?ncIccV}n&A^y{SeB$w+aO}0XPfEb1 z1%Rq<{LuqI;R;|xm8~BiG-}74*NwNNy~WZ9M&3LBNO#bZ^@BJboLOuU z^kE|ok(w3#VQVGn1d&E!@5A_6en3~dghrFz0EM!$L8f?D48;cR@tu19ddXQ^+`hAWP5&Y%DI&uZ3hu zB!HMukllfZB83`1MlHkWl`0cxu?M<)vHIf09-8yTV~4Tr``4rS5MXPMlWB_ZzYV7e zRqUU|(wF`F>*9ir#XPn&qH^S6!bZnvyW#Lv%j;|1T!&ZdUX&jlIja-)<* zIhfA(M>>)n7TRgeJ&8AW7gAD-ccEqK*j9J5FxvV>ZyTR%AdY}y@44pmJR46vK=oTJ zQ2nLMu~RS_QK?s25c#cJG6G2KbVi7dObU;r@JVi7p%p~um4xxgV3NFyLd_YBvM{qq znkOKt(W|;^Cs-n5l%plNa z(q_=v;bF=oqPJgS9+8f9fAff%yPtVPB>QttC9{uPn;Culqs^{N=G)!Q)1jxS9qLCr z1gxn#bgOPWy`zxGP*m4&Y1oPDKZ|NVy^Uolk~ZaMiF)$_bW2rJt%TFxCCJs~Nr+xG zRYKg$T6-J09kNPXQC^r|%t;Yd?C)bAk)Uox7hTc(U6;D0kt|aj{?XR3TTJlO?MLPW zuSQ=A3NCMb>}{s5kIP>kALLg`XX5hf%k1C2ko=16ZNG~AS{lisr22E}0#!4$y(4%YS=`Cof>g55v9g$7q^_$*t*?#TcozVgYm`AZe+=c z?q;ym5qCP!NfNZoXwjvQ?*ARN=dl2rwt2H}EjpGtMmA~-PjD1fS8%g%|OfEY&3$y31?jfq$ zwEH`zc9$hzTCa84xD4Ku(l)J4Qz9=Qys|XxZn}QmrNtw-98YIS)WCOfCcyY7CvQ5f zcPGtuNhK$cPw<}7W}dmm?#j`s+j(YB$VD04ue@NXE!BZ{P0F7jkQcw`g4Ok2zu#W~ z`&iw6YW=%)0V=>QT7(bafg@_mTVYwkO__OOAhuIMXktshhMOsQvgf2=Lim#`?h+ZW zacr_5{JtIYiHi5ZdLnZN<1K#NhVf!m?~U(ptnS2h62MpHxxsjJk-&8LDv|3BPmbRj zY=;kS$9J;keJ~!j6>^^Z74pwve(d9w`{@9d$bB?wcVa&U;dj7)nM|0;iFS6pBTwEh zV@5dhZmjvWdDB@AgF3$#E>#L_nx3dM*rh9s^JGTN)EMWvM|YlsP0viF@yyixkjc&# z=!2zt7Kl4`#o$zPz_QqNp3bipU@1&HUxaI;wuZ&oHd(VYQk^VG#Y|z>3>|aH%xb8a zXNI!4FssIDe-%#6p?2ow(;N!uJ#%S_8lbRVu2P!9sacEvl3CSU{?E(>oLDVs^|o?r zynZH(FT$^R^nkn!n@1V=3LKlCZNHQno598R&dO02PiAp8K*s#zG06t*>ABv&;E;r8^$3 zi{8tB*CyU{us)d&2J2OYwNHsD=*zc&?i=wrJ1jXbUN~EuXNS)J$pWul(SvY8B9P>B z*aZhwoW%;>KK2J$oIUaf))7`=?Ktulhr^4UV({2(c#O1wV2x@=Y><<)x*u%Fn+p$%qz~_ zOCv4zajq_4$#99lCU z@#lhw8T^^bkDc>zFKM0PrBkHujPqzZ9!1N-yIKM;171vd(lp&qPVxPADQo6jZBN}# zf1J7Vz^O~-R2u)U%&V{cOcnXuA1%hDw-vJ8?cbk}?e6?eS+~3UHCY#U;wv+5{W{aC zf{B0A8CUs`Vu(ex$NJ=B66t~Dm#zyVRDE_UwPD+QEI3=QUc#nt$>Lf z+sy22&JJ&Bay4VIvG;E~rGHP%SIG;IF5M-Ntz~s_JKn{tPVJ_2>SE@`BKc7{?H3%?l8$P!+_;0ReCivuw#NE_z2T^skioOcGU^I zZiy_)+of3Qi140=J9Brpad?XIS;^u0BLF}qYN#)EB_{OenrMLf0@SzKIhD*#a40cw zB22ix?x_C2{>XTjIN8DrG0@`Hrqnn2{w^Vr!dRH$1!%fmcGjw;45lWY<3>u$pMXZ+ zcem|*fAULqU!G+4Wp_L5%kK8#eR&d_ccMpTW~Z<_Pj}UG)s?Egq}c_?23{^x%vVY( zF3&)=RdKVljY!qa2$gblySk|>)EOs;*7qD6-15SV@-2&qeZ2Xzx@-Q1X=3~a*0DAb zUKvS$9l}pKd`*83?I*rZZ#|>&jJCPy1>&2JC#5AOei0ve^k?S(*dO=2{qdCt7KIH$nkIy4#|I)*(A5(AY{U<2AX&fJPPMarho3*3j+x?UG zozq(TopaG0UzN<4hw9}Z{_<`8_+9(xP*nCtm$M=0A%(`9;_*q_QE*Fz|94bxwQ6tc z#W%Hf?T}L}IV;#4-Hz*zVfuXI)XvA#8BBsorLs~gU}lIn#Sq;75eSknRa2wYd3$`e zzwgwJ51i!pA3i|q(EWisx^S*U&XFyg1eBuT0w(DU6qwQYQ<%$v@Nxs^lQ$StLfl*J zTKlZ!tn8166Mq1e#N9q<26H@pBN}<1*od_Ol;ShY69okXegP-GMI!t^(fO6X|0epU z_3+^HigQ$J@4u@fR@nH8qDENZ!-w=L(1?9_68mY6)BlqLr3S23Zx>rDXJd2?8M z3vI1kIBO>x&e~d62pp(ccyo5V-)@{77oo=4X-7Ckyg6xhWOb(l^Y+4x zd^7@_I^+3lGNx@L2B|!!`l2A}ES?BduzoJMkDQh_n@=3>SKKQmn6jhhO{iUw_4~nP^rpjW}+iY;Eg&1I>>k05af#obLPA zwr=zAsAiPV!{5+O-AUNjP$5hwL&NJIIWcpGT*_l3oy`l(n)}Wu@@{#H6_d2~EBDbO~!1f(C7>M=7Qx z8){QYP(=7yIp&mdPET6x&g-+r;ej@0&-JdIfa2>^)=i47LxbLRg{Hbca&E?R*fUJW zpD2UQ*tzn(PlS)^oY4so0ZQJLmZ;n1`PlWp;3nvKlRGDV6Q6Ybue!Un8_oZ>v$_4C z|J}H+wGN2vR;N>11S6-qyTP$N&|-^}686mwZSKVx+;gLZ zvVn>+b6X+3NOe~RYDohGrRLURdZDdN8Hyzh6qK4;8`l!}C!nyd!Mxo$l)J1-nc0gI~ z^gZ+;fQbO|wSkQe#+ab}0N#5;Z#1JG$&3>}{h&vNS z^>Y=Jn8%Lzb<{XMX=0HV&UO_8jYoLC)13_F0sg-z%dg1$78l*FC{nqcOjd3?1FY~? zG^W&UP{m{>-WpEh(S^8-Uj_4X^9_3QB}1~Z8NYISJsTveOcDb`Pr|XGPGg?W`Oup} z*9QL3p9L@4ubw>{`lIsDy_WOWQPtt0As~e1UVAEd%B z@~C!t3L05NP;_uN`nq+1N|C03y3>gZTT&0#CSD9kmFt`|55fgO!(iZ#=GP%ND>&yj zGcWk$PL!0E=%=h|aSf6$C?%AW3T;V4U$XUNiY&W6h8@Fn-J@K&;i8Jm_wpLgN|_v` z*pkL9X}+JMqXj@4&Bm8OG4%8kgJj4G4J#^cUE~rVYlCjeNjdVJxd@5?Jt(HD+NeKm zZ*1giUh7k|tztAF!*xl=QtNqY1D06P=wqOf5}gB_CnX2n1F{RhdL;^L9gI)xd*%|n zSjuBLD27f1Ik;qyHB36>99+UTVVA}v+`87KNK?hg-SP!lguz?JT#dS*BeKXQrHs}; zc7}0mm62u8qhw=)y-Ch;R~G!nmAU#tm2GI3QkEpg)N(?4)5h&~V;iSDUT%hgje;-A z0W|pQoJuIAHw~gJTD-Qv>2wZiO;8p(C#@C~Yfy)>VcMz{ZF4ITLS(2y_990l<`8s8 zO!`=d+X7>I!{jpgIXgVe4FmZ_XSUnG&>2>Tk{d>Z>2)ZJz|i}mLswAF-Sv` z*iZ)Bwe&AGK^AOl+iVL!rMG1;_kwLbG1~%Asx6dN<~CP1|Dg6`wb~jL;xY!I`H_u`<@tITnMu)0ut1ke=P}<~?jopN;D?6eUeCJTW8SNoP zv!M2krxaF#J7nM>Ub6!#8SwE-%)}!%M})S-u1yE_`(r3N^T#7-!LYTfMM&BagT5?XBEPL%m z0eH z{Jsqn?$T-VZKtTFR;hpzw3zhT*@prsv#_IY8iydjfuNcX*IBb!KW^*3Uh6^@ zst8WPKeRK1_0F4usOyeLGjB8tN`+Rt**JcSFyGccu#2a&67>m?<8xH|pOYqNb3bjY zJ$2W9t*!mD^UG>!#W{v;Pv_(e{iP&iSRQe}ie^=bG_}*IeqOYFJbbxj`&9Lg?KkW1 z8^o};LP6EGV4;ZA$4P!R)x;wD))f@8I%#Iv05?Lfi434sUC!;F9KUJ2J!{rG zEC~3*ZPX{PpebwXQ7wmF{^R8M@W(OYX1+3BLTGC6lfu8hfSU7&rB4@h;f66T&IiZU7{PT(f z0*sf!{v;hezw)9 ze_+Z(6Sk_+*8&F4$()$Viw$27*Es3zx=sB<4Y;ZvQ*XR$7p9^oH5_B$D7RFr@s_8X zwRio${}wC#d5tbPS~_j=#{uxsF(`+OU<<0(51@~)TOso%yppdagisvTUV|Lu^6G|) z^=$|UKk~B054GkouI?*#h<@Op1~>J4J#U1%9nkswCf)&UTB$B;^UNvQ!ArT8)xCDD z-Qep;);9}wjiJT6_U6rU`o{2S;dR99((B|K$1gdC%j&2YMA^~&qiAE&Wsc;Mm{bhp zaa8DQ1By?uH>m@<&|EN3%K{%E+9JJB>Et@)$2IaIXy`)}%*(C%BGtuNof-Vyc={1WgHgpzmY zV25y8Z?;i6(YB!4)7FXF`#Yf(V571jU`jV_~r~)|v?C z%O#h?X1Q2HH2lCJ%i)k*^IHzK_VVPceR|gJ9M_KO%8_)fyCMK4GMl+n9<|;_*1Sa| z4Q!iEn$V}SH?Ti(RyzKWjBMdP1wT@`KY|tXho!Yu=?w-IH)%!9*dOUuM01@>Gz#J; z;VQMVF#c!mPrbldJFq}`d2$@xyAcJ*wpDDOoSi~P!>ymF`QvFFc0cs46MbPK4mIt? zjNQ{g{q@<~m0R>E3iaiIcRs&}_J}bOrXD>6g|^z8`x!^S{?5A=oHE7@^Uo{cB(Yy5 zFDf%HY5}3q7frxPJP;dUF-!0XnH%}(zz=4o!ET?7R)EAA-Y3N^nY>Lr9(NEp$-wP;&SdJ3>9Q#|V5u1rF%2x>Gw@uP zJs1|^Wq&@v8`Qz&_!B?==#P5AOR}E&Gp{@u2Z4`9^tCne8(H(N{TbfleJM>9Mp%eM z+#HlcEj+2cm_iFBgB1>3yxM|Ka4Bn@y$v<)ZRiI>cLvj?w>EPxf@P1!vWFuZmvV5rk`ft6q|IR6j7&hS~>yC%R z@ks6OnTVt^!_-7Ijpj7WgkvBpp6~wsH-rI`k^&*I;wDH&vso1|F@vgXkyEIoV>Rmg z7oZ#WmWx^PhCV;m;qFiFh8Ji?Hzej6yYr3~@%7DxHxgmkfICqp7R6J> z@!;gckGOYzb8>ckV3{0P82jAm0wIsvfT>zY(&7qH+>JB?jf9&@hp_Qnje6b7v9p5Y z#Z%gLy?NZ?N-v$&%H;}r-!Hv2VKNB?BDU5W19N_v_8P~~oMU`m=J5*pGT78Vw41ek z*t!B=D{OwOx4ukk(vgMiQ?j+6SM)$Nec12r|Ge^L+DFCgwDYEUa>QN$UyAmj&M0T+ z!AV=6p^MDYd|i82dtb*^?mCE;jZQ3Noj5_kPfrLa`TOs`#e4GEGD4Cr^$+#^voYbM1M>_GNv1@e$dU=jl%|P{aWtOJ97U$ zo{FGK1x<;xJ7>aD)xzMEVqjUBDW)nl_#xa^a;LH)c^(AB=w{v%$B5DDF+_gc%Ud|nJxORad-baLy2Sh-zgll@k1 z*_p5H=KSzU_(c8xwd3%mwUH73xw^Zz74`qGKE(gKpU)$wHJ(qq9!1p)c&&>WTxgO9 z)9(8C{NG-8)~$5allcgXY7dmS2%k*7J_m}|9{xHWdGM3AF7G&;jTHlPH%iqbC#}LI zHR`r1o3y{xBj#8Oa8DCnrZh|5vg}-*P$Ec0OywX*crWOk_AK3I!P?e4Sv4fK!E-K%mDk+3hdZS8ae4rEIlAGG0z5sRaP| z7ajlT{Fl4uWwwzz|En<7ccSxuYj<j#bfdVKj&`~FFd_Wr-!{8=^fI7*Ej!?2;h_!Eduq9#H5%^YIMc7groK6+K#8-UkpmWimTc z4^^6;QjvJH#uNM}SZsC~L8VYY+bErZ)Q|U14$$+1J};Rh1u+{8TRIL1 z63Ib+25_Io(;oE(o_PP6%l_kIU{|0BW-XzK1yh~5w8Wz!cp0YUXy*4>4u}a2s96m7 zjGAu4Ux}f3cJ#X5BqVVbN+|v9PqE`7L3-IE zo)%2EB27*I%ENxS=fQXY>J$d>z;q|h#GPHr>=r5LOIbLX&yW@k(A^vbmta8aL`Sbz z23e;ns6sQIID>JQAOa9>Rm$;CF7O1bm(X?v;San%%%p)k`p8q3CeX|Y)3eAH1j#HX zXv`GQkpiQPaP}#nfN(x&NxH%Eg9!duZG))Kb~?QbQP6-*Z8Z@CyJZmJ+qrgxYRoEO-lwzN7de@Eomu91e;$z#ND- zen{wQOx~Xlkk})q?|CG&Wba@hoL|5~G{oS%fFFPgG?PtP3=v>U=}}OaMv)Q{HNaF! z+SuSU0caFo^^*@6cilO1n>VFSkI1WZ3^E7SL@AC)E#RpD;~5#KaFQRNL}r0<(@B{G z=-W3($6LUcr8&!Mlgq=-fk2W(0*Ifc{%i*7g%a2_T-YHbI8N*lk0k=3Fb@oemmkbg z>LPuaAq2&s`#=qaSViXp0@?sH=*$1O zus0(_2$@Z_u|H76UwJ+Kbu6-4gP4i!nVu!M{Kp3Xg)b2PhHmo}dKuOQS-wehMO?L!}oY z0A?UWRa@ZCM9_MVlG&A10bztWOX<0$$nlOJ*Tt|N_Ujn5mS?!IpTp#I-Zc*DowMVE zlYP;h7TY*t9YneYk|brJ?!^!b)FOvwjDa`v1~)YQr!W&})iY?Kt3rZwN>TQ)J`NM= zXaAYIfxiYCr8gM(7r3m(Y2u!rqY~uKNOK`(&}1u$JJvx_;RTU)1#^2m4~SVvgLLY+ z*5QISx*!hZ#R=b+kzSOt4)YO`c=lXcmXSgk9gW5_S^*GV#W|4+SUG`pBu(64%@2fX6sj)eh;itO zw{JN5RI9l1ca#S!q&ClIV_ZVIuxQ)>AmCZv$B`67IFyf2PzM#`*-h!0vx2JzLE-th zqCffM_b$8{#_iW0_9^{IkiBN4x%eWqsLLaV3%)*m3pU&|B}4R}aeUY~#?$9_b@a11pqXq_9SyA! zFSZCR!yf@QK5~^5t|W0-ehPgcvR&wlJw6oy=Atw`Pab?=j!BS0*d-*Jogh)Ny}_tP z6!nB};KW5XsoFM4&C_O1#G~DXki@KW5*G`y(PBg!S?rg1s2`ZnW&+woA;#;^rN`0V zF$xy}Crab7bP$51Hs&KAwyn{fMzmj1nY&o`v1rsI42lAr$S##c9&imN&R!FN8Ug>Q z5}4SRiqj&=h{A@18?is=KOS-r{v!&@+1?W(8d!(!ZAf|CpRM5*S2iG)PHqB4iDes2 z3~y`-gV-p=BqZ~`+BlFnX9Zo;zo&Cu(2CWPhfdH1hQ;DmXY84^_l@IyMd(GytWm+i z$pbUh>m^QLF~Q5CKe*Ti0>YFTlwWAviU>$u%}1j_kFz}DRzrY`Q;>!@x<2SYg*2B7 zAb}xKNH^`YRZLs&h*yLeM&C$D?09%_<^UuV+#}oo!O8`10zpbPwTXPbbF~1kHJtvy zy)cJN4M}A6XuZR`wFxsQB5~EaFr1_6L313Hmm(%R22l_Y-BI%_g_As}2+NMKU4{pg zX;a&i63ND1F#2vLw_u!(iJ$;?e?+1=2PZAsbd*t(+&@@@&S6irZWwx+Au<1__P&O? zW9_VclBDM9G82s`*m2b4rXV_Jn$3}dpB7)6y)o#<#G67nUkhe8WCc~0@4&9< zNI9yhtP)08TPduUE{K4C&k2u@$nO_(+!XxpjN4P?adcqY7Kj_YWMzOw{CWa=EPv+w zgVDg!D2&*$A4jcJD8xx;70UnH&Q08K@3c5^Wj}98SB*Lg(lbg;oSYgxf6h%$gHUmX zMS*&BcGzx^BCvmYimA^-s*1^^Q+CWZphM1`p;Zm{U7)G^Yedog$B$XqxC2GEb4naLA)ew=#GlJ89h+K+y0CefN*m0``=p-%pd_>7 zYT`(3ONBI|YV!V9ZpH*nU_}NYgE~)N+6>9KEYJbY1G(d(%8@|on4U`dSO?~TP2Dgz z$i(U}Ot1?R3l||RcELhp4J<1gEz$1i#tG}mTD(h~(dK0>+;Nh|sG(-rr%GvGKdm)! zGa>fMr?t0sxgR&uvZDdcIF|-+g2ppsaI(2Fg0{J4S++%Q#QaCAx29#`im*;g=sF30 zJbW|G$mY{(y~(V?H0vwlnvO>Pv!7v? zN}S>1?we-XY>ay2e5Qmf3c;U4D0Eo$kMMWNim_1Wq;M9t1{#;B(&%1H-N_~I;4g)Z zW5vLd`t~E>hU7)meja_N2*sl*xzCk!A|(-8njxd3FbZfZ+o-in?Lz5NEx}9}h~WrE zx+83l6saOEk*4Y-jLydK^ctGqSEmEKsg78~X+|myOxU`m*=R^+G!Tqyg8vv!=9LeLRBx?VDo1XR5XI&Rs6{Qlj+Vcsra*%1FlP>*1Z zfGkueTtbv3_`n;W)DXMSX%OxXIj1_xmodu@bQ>E5%9bQ;C;p5p(mwJBwk!$(efY^ni$CAwq<3_TXsK!^=pGDVaf+<{5>A|fH3M-z}9VkP&7!Z#x7 zZG$CW%JVLHFiEy&Y>>8T3L!ph(NECW@S(Qv*rh-_M+;B;o;i(!!!k`c{>v&{+1Y;t z|CdqgD?0~e=k(nv9{rqsm14c?vJ%r3EU!H8uVTUpDTzDgaIDhw%@0I&l4YcWzl zNdZYbC~7(>v|r+GR{CN2eWQ%1T`IGU`x)-tUn-C<_bYUG;RyGasndn+1*mq4jI;%2 zmlq5OB()OrVr+QA8}b;N17@__)@TcCUjNXpAB*VZr>CJ)iBJKtJ!G?1`%u{*S@REO zy=TwZQdQ<3S7b3_sZ}EW>W!(@Pwq$<`=qU+=n{VulR!sH!sd3U#1Gn6M=(i+UWsV1 z=>w;@O>x&uGXrm?Mv+Wt>i8&dFpaj$u25umHHQ`y__oE-D1F zsL`PG+_|dI6pp=de*0V!`yCoOVl%efkV;SNN2hn=!N5HqPo+CX&qr-#C{|s6bTuAa znL|NRSDr+%9T^>)(3QPVz%UQ(#&HKKbXsSpbg|8}aLCa;^1IV9dL@CP12bcN|FGeN zGdm=HNP6MXpth0#&*KCOIG0gwh`Ee@1F%5?h&m3AMfkTGr>aO!nIHHQbHR$9OORke z=tJJ6vq_A0ipZa=j0ORWHai9gvXW!O{2X@ZWNn~5AGu{s{VQBR^noKcj?p;W(F3A0 zarE)gyCIJ&^13EX0JkApKP~%s6iWJdG;j$d*@}}GeH18V1sa0kJFa8yO!%k>J+$X{ z5lJY<#x^V)&=1hXKp^&l&>?dII_nMXg=bN3726(FYS^Zv)b=*yBr}sus`k!rwC02^ z9rg7PQ-t7?0ToF1Q5+m8Ku81VAqu|>fio@I!dsO*-DpFCDX=TNi3OnW9||JcWVm>A z2>nO4M#-cM<&2YRX>{|+y~*PK;}hIU9~`QnP1?}vTWPU@y_k;Y;v|4IXse}Ko|zsr z&>Z{v47RLqnOSq;8Q>7DWapl4o{+Nz=WM~&Pn&=^drhx#k+F;Py-{nBI2bLl z3Cf?b_+&o}5EEBuzmlNIk+e3T`v$1h3~tKIf@1Ga=Sb+a#N8#8P4w~S(O82bH`geD zG&|b^`82bP2Xv@$p&~h&qgS{e z=;KZrcO*%Qc4JYW-A!x_qQ*`P(91XxuomI)s;_048JJ2qU^T-oU zjx^^GaWqibxWhE1wSGK$eR9~@ukF99voWg&I?8m2#t|lo|En5jvgnV{#_V=4J@(@p zlWr!eh`mp6Gdv#;Xr24$c@rcE3W!WLU>;lwx2BI3${tiQ7UT{5iOY(t{6 zVDz{IcDr#d#$ynQqw)OWlJ_M83@R`Ufx>hfk43}W32GGCh8#HykM#8zP(PmsHx4FB zrrEC%j*c17VvIck4Kh%MRJ@K#r`){r!?pBclBFts&4GaGbFYsk*WoYTWf5Q)r(<%>gw+ zGCdE*=;b)O9D)!I03U!djv_;}gvL;s^74sYpmX{;yTu7N5A4VsT^`xerHuse6z{_6 zl8jF})^I*>(Ft_(|0`B^AID}yiGcvle$Rm|An%MfDwU^YgJJMtnGRK^phZr`Y*oOa z)DeI|-2DHlyD$lNcXqaRC9Oc-$7Z6C_Zv&Ud&1dE?)T=2U_d=$QT(LE(2X>WpPaUQ zGQ&spQNHe|maejLWCCSB;);)J!jwsHoj5A=q2Zbn2%5@ ziA7xYN`wn181k7&mY^K9!tT`BBa6jJl8}{qH}}qR0|#TmV+36CylWr zWx{JeQRh{3DinIFZjp{0o#JClXQDg5cnP8{%QTI$wd0fH&T;FswofNxkr`+4*0$_+ z4HM5L+O65CQ$IM@$3lB5=1o~%;F)T}lfg(cy5|!W+7Z?S5Yq1&jp|pv_bDg~_Kx10 z6}*p%gS_RUZnX&u1FBPKTjH}&SbKE29`43~z81<^B$`$#QJm$ZuarRQL{Pb#;XOT6 zn&j0^S_ChB=;5?`d6SFEhO4VZLJAt=A)};nrZ_= zRw+`?{*M=l|D!E%$uLUiB9CFkPUghus%P(nwX2Qe&g+wd9}^r`tlb>l#55@K!U@F1 z0-r8*v*a1~Zp6ARtRlI(Ex^eRtJ~V_*eFcz2<^K-yB!k8NRq9nEh;4C3jIi-5@VLDNV*IbPyf$Fb?dFO=s?8>}LxL4OBno zaVk$W0xlKcu^jnU>&~k`_#h`g1;X-v8Se?#jVLP8e2z#7`U~6a+)|2 z76ytqsgl(@T+mo8@TX)Ok@p;+2jPk?d=--H_vX0Kz8Wh;HaK$j>Bw8V*m`&JgGw*O zv6XYV&_#Ml23vKrK0Fgu3zrg{bV@-ZY172mqlM3Aecb02EC||M=-!-Q+*npAMd_@o z7ZP{ETFzg&X_#F~vxJBvk$^4Cw@Y>b9C5AS(w$D6b5J}#dgNzj#Vq-<&6|^)K8#fC zCx3*VP`|JZnBAk2PCVEMPhOnzzKnLK1=_XQsU@4BAGf^8m)p;>7nus^JC3D_a^Wb& z4^AT2ECr?{geI5nxhHOlAs-0?F_}xvg-zclm=C)cy${Kp{Q5_2txP!m6`V-xg?1-bIaA@1ezT_h4jI$3SXz8} zWrnxGVQ=|Z(&%qeYqBj;r_SZ&M}$geW8<0DmOSNSk%G>&;k|8r6DLmXVTX64sdX{s z!Jvj65{$;cm&%^J2c`N@X6FF8i!c~~ugDcF0}8*}|g?ckV(Vj+(iInCRDA?Yp4w z(Uq8d=zHXZkRN@CS_=BF&)(hv`}6rlUgV+ZBDhPu(r`bhpEm3J@a^CZUFhNE@Gha7 zt0w^CXRS`|4w=Xr9pc11ba|We3?}!ai>Fx}=Oku65Ee3!Q$u(9k&*rxBR27KGF(qur<-HvS1q5bBq)tvx9v?zKOh54DRF(||F^ z20m;gI|dr0s6S&V>J3LV(r-z`=o(I(yi)Fq7W`k)vqOVJhoA$2raKDn4CAUs*Xw7| z-IjAkH&qp`8;Av2Y_vuKEu4jP^kqz46(Y^V3Lc@S*_Gd$6D7F&Qr+pZk;J-Bl#C!k zZ4G3&J?CfKj~Z=Eou3g??O8bE>>G)xci~QZ;#i-z*TOXn?`&CzALioLhT&&~+q$zB z@KLzwv;;~CqqXtIOc`-;37H8y*9`r41^MjSqbCmr>Db7!i3V`tLM!e#N4K#QctFl9 z<>zjjzR}Jl>?WjFr9U#gFQXw~DFPPV!9%AzEeIB~_eeSBDZ=V`qBSqB&>%rrtLx`V zS1UY9xUAGRPs&-3jJ6c%r7_eQqb$g&InPnpe7er2?DfLuZYbOsL=iwz-k`^a|2y}) zADzai=Us>?L3PKG3fePP3|b<)b6w zWIXXk#gg1FvV43YzPxf?kSo#}V)II#4Tf+1Kpd&?0wM5AARpgw&;iQ&@)!}w8+tym zixbAPF|HjrKLBNwUzzL~I$N<1GkRn8K#qrj`OH`oOjsG49s_NPReOYsQ9rzwNJcJ8 zPjsc=u=eB08K;#?XOMvlqHrcoRq23RUGqXWbL?TrE#fAeRz-|7ci>_$K6eK~>d&WY zi4Ei3iAzcxVxD9lUepl}#T}dx-!Veo`{dpTmn2f~czx1Zr)46H8x$f+XC*YhK+nRk z9^tN8oD;7N#5Ss;u0`^1;%P-6-L$U-FB^+UQ5fumZ~jRZKP2=j*TaDk&qaQFcL^o;B*$y#eqMpg39| z_odG~V-Z@qbrnUQj;Au*tQHN6yltRUu%1Suo4~RW#zkdpVjq2D{6npI+&F$~-33H( zmgJ1#4zO&oAQ!})4H_Z#3biQqp*Dm?I*m7cL`-Si>j&BH_euv79l{BRa>?eRKsZLu zr{gh+q}z$O18>A8<2$&4JkqwJZ0{8wd)J%IlV-j!F0wb`s^nxmLwytk8Rdhw?-|QZ zfWco}Hs;7zTL&eZJFeF@Pcgw7^oZXx2* zg+JB$h++^UAk%CiK2Nn8_hQGdh#a6hrnQua;LwSSx;m!XijJN1%%aLPxbAr%;znCs z3}O_sivzp^d{k@Ow%%fG8>h$vO(&x}`W$D1mp{I9Cv`P>e-AX+Xn)4Ru?uvYV0?~Xp%$o&Htnt?22YHPAq`iEcF%}(&zNj0#&8~< zyv^31WH>F+8>WZY5Rm)`M>&j~uLAwXW0cA0y($_o`%VNi*ilR+FG|e5s*=FQ6HT34 zX8X!r1@p(I3_xfy0D`ZhJk>&G>pp+lw8kqDn#U?DfXJmBpj~t2pzD<@LrN}D=}smq z(mENo!9~{R!E_yyf(>tkQDFr&coaZ0{mQb5U_sp1k)C=5OS)W5t4|M*Lc1iHf*?#B zFxE{UO7SMjNO2=B>cd`^-Dn13P-+qJ#S;k%B-FA>RX`^TB3Xgj(GpY0vKi?I^yCAy zBba5SAIP12Ky3O_(+?2EBe@40BmID|9Vt`e%w$XHQK}7VS%n}Fw-wP80Cqr$zhc~G z7p4k1bQCsV%63qYhrKTD5+^RY1&y2{ao8%dpAajnl}oy}2ZM=2EtfDJ;x!{)x*ot? z3LBK1O5B7oV?}%7I+cfKxp>P18pi+W&-FFqfqx%AhQI%IuUz=Y_wf(C_35ooZ~f5# zamD}X&-FFIyQCtQT%?CZ#`L!vz@N$Gg#T_40Bm-M|Kp#@4gBdp-4MH9Gif%Dnu0C` zNVC3w1`EP_s}v{ALk_~HOK*0`_E}U7g88{QQ$y(;cxby5r@@*8L~ZbsHyC89?}cb+ zMMHgm6yhA;eI&lq4=O%zO5hY>kuEY-Py*?J7n-)1^G)%|hBJ_#lI`&h(%?qcmbXeH z(KlVq9y&uy8T+qck|I*uRp3yyWmmI~Jg>Qrc1fvd&}sd6bl5olKE6%k5GT=5mr^<+ zcFf4WXc)WpX?jhfGAd zm4aDh{nX52vfD~>uC8MR%xxAULo&C#J~_1R>1`BaZeU#jAQeM*gRVSmfH4sxEA`R%|^Kil7nxCn{(oR zkj23BghueQbUqYY=n_pw4Zny$vCYMJe{xSrRq^+v4B-4qxMdZ=H%=H?#9VAG1|M@# z)qQ%Bmeg68r%kN`YWVOdwKqoFi4s!koPVl&kXgphz?H5y-rigo?|;lb&^W?W@XKa? ze~5vn;=FD>MK&e?Rn*vD!_T^&vYx!iiIcI#P#Q&e&LkLC9V>;So_t&+>WC)T-fNy4 zC7c>@#e_p6@sbZ@?2GP#heg!UgznHpkV};wv@PC2ad;#x0LJl=P$C(xaB}LJlL)Gz zxj-Ub?8&IMKH=F>bjYKi)743`TB1`L1@53lT?nlLr2yls6$T|JXX-TE9y=nJ1Wa?{ z2r2Z$WkR+p@qr8yNzcCEvT~uqxe0vea)Rk4Kb{Vsk$IM2zZ9@Ex%zT=(}90F{HDZP zPJ>o`%H1->#tWR22|JFb{Cc|_=-fU;-f%LzDH~9k``D$gCj!0*1)KIe1_+A`7DYJS zdD6v#vT3xTp8ITal|fc?oJ!^qRyb)RrXLO$#TH98f#c#Mj?P$yPsg7EPXt+`(gM-f z&66Km^@yRq6n5F@PbgEDX==)0>Js}M>IIQ$ne7)e$5?|V35#gR(6M{hY!nSIUc!6} zgA>W104mqd6jr>oFe-{#U<2hTqxqfkdBD`~$q1;TaT&tJcFtSokXpe!yxH`=-h5qa zH;!Z$@VKwEK>}m%%f27V@kZRF;2Lqa8~uV9Ud`iE*YE^GIsJQ5WgFbsopIEq4cSy0 zR-9{K{{&Rw{gWd+rZP}7&ol(0DteC=>_lEaveA901kE9>H^R9h#Xdc-U z)$pvXSjBzVc%X+?4DVy9P__C}f|oGbvc9Mss5snjkI%jfluwOZQlaPt$37x+6Dswo z263)0{;6EfhJz9vc*yQi$W?jRwq1Er*~J~mrGIfrH#$NZ#%+-yrDIb4Hdh+8bX$PW zISlp?24Zq!OEshBCf|2pW50;cF;b#mSB5L8sR3ghFwLsVT<&w8tQq$;WCkztHC!e# z;SkmiPv6yE*V_%gHhED0U=tJ+8(u?9pd@ty8JaCrGBjPJK2j8y0Weka^w4^Op{-_` zA8}ba5sTP*0iW@UP$wLbK8Cr4MdN5|5fgYC#Q`y+icRzW$zcm`*HbZ#L@DQSp{IR! zJeUth!sksMuKUs3Mxgx9oT9SwiR$u79L3W5samOQZv0Xz6m0lVzjvtH^3Jyw0hSz5 zq|-AcK^BHrQVPZ+N)h5;mqm1kXuJy4_rnN~6m3$>Nr7RU#&Sc}czb-(tana9ayL?` zrdvQ5o6tQXw3*85ctFTO=-(?eFdkgSjE?w>wvB!6GN)3`c}**b^Z;FE`ys2V%PiE^ zf+<{;6xI;cFUZhiZ5`0go$!?iET5DkIm`e~aOei*B00ZJ^GqxkcWCAi8IVDVp^Vd* zD^ZLNGediR@+Y@jQMDTKs%o#AsCuH4GvX<(MgI zM;*@`T*=FLPFk+G!K6f;^XcHs@C&p%ylHptNh8o>>=$euq25+v-Q~qH!yE{c5PgEU zCvpu>?W(mi30(;XE7<+I*u*N#rpHY1C^>B!Wx|x8x2cpq0gjhs4R>|#IrDz(g5@XJ`JI<6RAJwN?+_gIA#+WV3Gwx zBNGQa7A$SMbSif6<8ke%v2R`zO&HJE09pbe$&r-zM#TvbPe&15lkVDK(%kThg5(B_ z88#TZ6qo4@c~tSZQXG0$?nA|)w1@*#;8Z{@zA%Kq4~T%i%=m{Ivk_waX#6YS>N?fR z#^bW{21@kDxGy;NW*~7Up0i6Y*u%`5QKWRx;HJ(FJ^y|@`p?{(VxA3v+H(8AU?oZS z7(t^v6kgsv*>BY?Au-C7$%}ncg?cC*;bQv~gS=8H5Vmf>tu_G`hi&->vU_e7IB(+x zSM7OM-e5c-Go3fO@-wJL;rm0PH)x^m@?jUy>;mV`>Y@6{li}%dBRvDOkIKv%M6_xVnztw zv&<(P5KP%UdJ=dAk59Y`NT_u*rUdImPQaRx3Xt~liUsLCeYw@ZLmYHgj@|}mhKh(T z3KW`y>D1|4#2H+RO^FiB!>L(|I?SV4MrKW?{E5SJP>NxS@eYi)kW%KxBnS3Zx>=#r z8+a1f6!yB}=7P(zd~}18j#&X^fIss}1(g<>Vt|kk#nWYfCbqaa2`e!_ws?CSx}`cSl00 zg8T0{bGR@|()CuK(t${ae&7sWn{!6ATNTDc$}L~__KUjc4BY?~HpOZb@kE~wh)km0 zeWCe47DDCfUU;P2XUHWU$PfJZ4yn(5gE9PADiaYfETtX`7o($t8aniTcG%qYkmADG|P-DWAkMIBnL^!4X|XU;x|3cR6=YYUR7Sou%)vJ_i*q+*=7^dK1$iS@tl-kM@G z>S+Z<_TMdG(fN-Eu^>(7Bz{A0cs~e@qZZ3XpF&UI6=sQyWeI6hfAm2$H7}{Zh@{h; zlk=07ZIn#}KzgFLP+yeE=Z!_Hc0fzz;Ib5}PAKlC#2=N#+7tUXQqyHQ^b@9g1Na;Y;(W%VOXPftEfCAHs)&=POg%Na7)7;H z08{zTg-Znx4iXBH-4v8eRzc`RkgI8zY zu1aUb14mI8V23Y48Pv!Uiw6_f7fECtby)P#o`pv)UVkjkMTvYIr7qBw6P-Ik<)kLe zA<`J)3y*q)x3)!;t3RuBsCO^|DIEPS8>f`Jo41$UEfCH%p#iN zLRY%wc}kfNQ_9YMt0lt5ahPWPaE1Kj(K0)PZ8`9|087UNA`^p_e?cW&Jm-YSI4$OZ zF=yE@$9GeW;Indw4X%BC!*c;apa-Dvf5yUzQFLf6ql~oBL+uJ&ft^t47NPS_1*x7; zNDP^iQEgsz?@3pLNS85R*&9*E#lBTTU<@nb;1%n&NR8C7*P+Inuqkf2sq}r~8|oASTWHm5&HZ=v;!T1SO&Bt)iG=(oYcjbL9JBA-D>0|DAFYv zaEeZcijh+pnhWAK)nE!!@FwiL@?WO|I*dR*^1?fPVJJvFVpo{(HO%*lvF?pE15Fw}4Cmr~?1G&ME9&WX(_BIngLrW+;InkBf_2KDwDHd=Ai-fT?B!No5Sx zVh%7})x(G)oQs&^LJ2&D+yVB$R=d37Nx1Np_U4$f$nTX*+vesD4!YzRpKYc_|;Exz6^&q5{36;faj^8a$|sbrcGZ62_rmL6Wo^x8mon`ySQ~Q_*rF!DxRdLKL=;#ipg-7yU)nrN+J*@GRo;mI| zYyh`GBlpT8haUcW0a`lm+C^Z1D=#{lTq)|~r{QcIn26$G9Fuml)&T;1Uwd0OF2yI@ zVh{=W`aC-1me>6lMuAN}8rD}0nQJy|ICh}Jvt}WeY;M8u$uMMxGk+jJQ8FmpNK9rT2b4T^M;OsKU*XNR0S#H2wqv&m~V@5uOxK z`bNr>m`k{Bhy}6Oc?ZMa3`mw zUNJ8jQPlJc;gZ+J&PUHfMOgY^0=J9M#N%#vJ}nztir}L({Gfq|Mj8csGzl|p^MbJm ztz#Z{8cRI#oLZDaTEtB+Vv2DN+JnY^G>H}Ora)?sp2`&$Vj(bs&{q*tPjIq@ zGM)ER@?G(p!fF*%CJiMqEeKgr7Wbp^vU?`R5?4L?0mtyX0rt8k<{QBOL`It+H#9Re z70IL|E}Kfxha1O*gWyw0l`$DvMb42x1)esqb*ujBz0v!-%xy=`eNVIf=Dv14*i^E{ zWUehbS{x)&J#fSX8T!8pDgMJ47NDnlN=ZWHmM*(c0yhx&vl$;9p{pYe6hfQw?L$-o zyq=Fq4)x6xmar>M@Fw?83ZZ<}ebjW;&$WxvI%R5B0KlUDrVnMJ@+-)3FRHIOfKmH1 z_BUIRpl({!2;_qj37-~DVe8G-D8U+F4`Q}oC=nm zp&t;%bex3aoRFHFR+HurpCogC!z*OUt?Q-Ch9nc0Xc^0dex)lM!ubJq1u*YIr5<6V zQbMPLKt|mRZ5hZcQ^x09VulzhpDdX7v(6mVED>XJ+SVklfwmMNJo{1rZ*ngM3i4N5 z3jl*$Yk}4)Q-T5jz31j7>c|C}@XoXpprCond#R?19a5&K9eI(hdk#Vj0|(-DF6kFl z*(x!P#@%O$V`WQY2`rT$;Q5RvzF?_bN9BTuF(hw{Djq@0f#&IhRm^}V6lsW4GO6f8 zDrd5&fgLUeyul0%c+zKxWR=iiV^!IThdPajCOTA1CblxTOi8oFT!i%)(^V~DXq%!n zPjk44rm_o(7h!|1rK*0ozo-l!tU(!)ga!~@`r0_cTY7?r_=1VGR9k}gML;%rT|*C4 z74vxhpvQ7Bq|b*1LOFa8zBOM@`dsIX8b9&f6dOw)i5&Y)uVqc9$ey`wmUQ?6A4VS$W+u#W6O`ydCVQ0mK~ ziD*PVx^)eUoHkG1Hfu+iSr>1HWKM$Kd^iz?V2+21ZXmP=E`|(l*?5aHLo>i3Z^)H< zQ7Jz2O*aU2WHM8R8C3ZEz7N#nSb=!GR0MLBt{D-COqxi)ndR;;vXt~%sLf~Nw-|Rz z(>?D==GD&HC-9?lP|wASY-4K zG?%ylMn)HXCDO*%KDtmVKEAS(RZ4N;@kiJ$SWW5Ak&ncY&?lFpHX^y{u9l{Qh$%dfv zxp9P&A-y%f3w5x_%K1l6+y2K6P;fU-98;l^LRz|Elr=5BzPNTTtV+DUkO<*{_WN2> zL{p0sp5p2FeOhv7wpK%TMS02_a7t9g*c^@rb8wSEh_^j7FtAZmUDSwV;`9Y95&L;$ zDxUpK$2d*#Aa$7tgcGcOOupvOQBW!N>#deujY>5~c&Kxed<6#FU(?YRUKpUPqtY-B z)6uIl3YLT-*@D#mtXRcQ(~fHU%@e+s+Oh`TH6N(Xk7)^Xw(vT1oE zX{(DD1jHVg59B8PK#Q@1vc;RC)Isl?BW#Y_546OXy)paPyYe3LfbF$tW4=)-;rZHa z=j8bCN2CZTJY$jJJ4yk&6tzG=LRiShrbD`;8xLR1Qo-yA1)LoEhW!k+WJ|Us0K<(p zj_U`6fWWKP>d2>v0(mCH5VmxS9q~GiW6BegD@3g1wLni8jn4%T&R0R|;JG&#e+uK( z3)e`xwf{&92TNS7oI*onZ1UA;BxlIVXr>O$I4`jfG4W;24tACbL+PXGCG-gqQ_~Y^ zxTA80LX&G)Mz&r*n#pHWj}}fwS5OHP0+lM^Ql_7oOl%2g*k{8?KvQ^RO+mw(__A;b z60JlS(!@9nbuWB(>>UfY$r>l;97vuyEO5j?k~@hAq^DijG6X0okbI}BsM*r7+}eRW zdM8cwqT3Z7Anx>nlvBBsi-{42`4@9k&tReQh;!hxm*H`OQyh&&LdlYfzNfvGnOk3q z{L?ah`O7Lb8U%Bafo(j(@U9RoR}e!3LdxQ~$6IaGtokYm>m&NfnSHzSX#k4}rGi6Y zPQKAhwP!CKjM#*CC}~=&V=2Z>zQa{M9J?gjLR2(r(#nv-Qes9<_zN=HU7FLBF&G+S z7@54dlk(<%jBL_cZCzVUcQkn!B*wc@wKkw=VM^nQwi$|NO>V~s*!sS4+Ie$!Oaaz9 zM9zFMybF)Cb0T9VPp5>Vts4W1PqVJBq=UsBa7{oQmb6-2+*BNKzFuTs{EhG=6%dSe#YmjI*ctT6DG z2yt^oG{T1V6)dnqe7VAJMdy`$9mCt`F`Gp4u(ReNueGkxPmGv?0z;>oOb;^%K-QK*GS5x5l1Uqdd@(j<#? z-ZoFpPBVK!ccqg%!Im3qH_Bl@K%1c@R)y8j!LmB4dJ)CXw!c901*`35Z~-&$n!2zc zWZ@*V_Z2w!=oFpMD3ceusPJhn1)qZ&|65xKGrLzhzy#G85x&`HSZA-sM zix*D(pv&pD#siriQ_H0@?2`BxRfb+={&AfPua|^-mkOLqcnQ-OD2IHXQEhnRSFev+ z`*;SV%nIVZBrZmg3rkvT5?p+Q4yq#!GaUoOhI?raUPCM-(g}$zomjT@tw$lewj}H& z(n&|d(6Z%<=MxO)1D(5qqT`;A#cb#CXX0P7OI)N{AU!@|8H9q>{6sFexw#_00SPT}N*WLi{>YNMjPd=lN zLev-G3xo#~axf}bBiI7Ko)9!Mag3qM2fL*dXefyiT^&?Z2+1!7mDo)0a*zN{f9AZI zdS1&N1(n&XZ(j8)#3D<}9g3JZd|)@z4Q0KkFBKCSCDxdZ8H9c+wMFvrm3PKK)1=cd zoTqM=t`%H@aJs_#Vxab^a0fDM0r^Z7fyd}jhBn&T4jUvTx_|R6i4m+5GQ+bG-w&}8 zsx8YUpeh9?z$<|4E=q~F=nn--ka-fZ=QwMlK2-=Z=DYbiNCSPa@yEb@*5Z^B4UViJp6oq#|8)E5!8 zbm&@C7)M3r1XWQ)#8PTe31=b^soq(GQ_MrH{gcDP+Ut`hEjYg)+Q?`WJo))95&A%R zPr$>MXBDQ5@-9&_){z32BSOuUku`-GzunWTBSW zx9MbojkAtM@_?Tm9d-^+PQJJ7AM7_-Bgzg5N?kuvYQ2!$6o%eBIygCc3%kv;5t|W? zU@;r8>HW|hUChzRwop)b+1KtnsclF?F>694?cAGv@=y;2m2*J;&Ejam5A%W`7qsg~ zr-yXwNmI+>8+ywbayfctSXL)kG)!fKTi8z{m0$ADG;z9}Fx7M+#0rNo!}M_Q#?3H2 zk*qj7H#(Heb&HXQvap6E0x-F!R0fF(hTplKk<*}5q@O~GlS!7vk&l*iS=#vgK;ANm z^&~<3)(#JCQdern_S2rEzII9VltX!=_nmJ0iCnDmRDpLQEDML;>~h@GUCT`WQK5aO zxRHt_iE=cNwHh|5u$#$HcL<0~%D ztIaZ2;*QD$I62IPDWYNo@Jx-B7HucHJONh<|=e;Km^ldSiNUlz%5Oy`q9CKi)d(@f>0$YGxt@oeY8+lhbQVi z!f{2Eu`6EFJareAE`?A;Ytm>>J|!?tEi2s@^8t#OHQZ@D>Wi7*fqa`-^ZFSo(@HCZ zM2XQ4(X*q^jMX`+edrvYNWZHD?isq*{%}4FgVGi-TSiZ!tDLJSN`Z7S#kr##O~P=t zTrZqnbU*^A!$XQ0M&g2Lt*fuA) z!h1&N<7->`NM9!43UL-nUH8%-^kDC!k~56QGlm&KoEjcP!W(s!LAQ9wrX@emrzFrYjP%s+ig}HVhA14=PuuSVpGT3R3Ce*-$t)eO zn1@$DP~t975>u>waZytfCDo0uEm*Z(na+?FMP`{P>Y2i4T^Uzt2+nWB8M=s5gMtEG zj-Wm*(OZt!({G`1*i{(%o)<`ZV#HxdI1~D4ti^;9UQs&V1e#$1bq^JOriwFvlfnpG z;NdNWI}J|<#>2^6%x2Oq(Bvn)6^L<2JV4WUE)-}SaJ%uhGhoFz6TyAdaBT?kkH+Ge z1j9~4Hf|t>*^rmcXF|TTo3-QCX{}j5HjU}pbpsLSRk-QQrqC{iNgAPO#N#I1kE2MJ z=TS0-C@~M)k=4=EYSkfemn?+cC%5BBBiTRX}r#Xg}aztpXK$>rhud0@(N%i@&UAK{aka}y~wcW}_Wn*3au1vZWf3%b~HsG_ny-okZ zC;IQk*3RDFs@vPu-OcLG=HAZVHmcjZdz*iAHkQ(Q#%GQ$$By$iT*lJMWxfA{e)s~@ z%YX2{|0rPG$Bulc{rE>=;Gctv_wpb7?>`Ds_r_=8_kR=$$L`R3<{YsH!YMFQ;lN{` z8}ztzUQ^P!HFn4HFk&&qZXTag;u{PRqt13gKvD+W!h4o?&zyg}uQ$<4`5%Qt|2%l+ ztR4RSH%92Qvo=u7TiC}$a`?XS_uoR~K7W}x^hf?g=YQ?2{qCgMT520v^B=$C{NLN# zscvs3x$Qb{<-My`d{NLKzeHj0H`TR>Tz`tlu zwO@D{{}iP7b%;frpT7C&^&B?&P78Ks^Wc{vdVK}Y*4Hm!i!wi_tY-phs7|b7l7>F| zYOU`#)h)fVcrt4!u^DFOWJS7(;sO`jd~rta~xrd-|V$gTNVnT4!w5B{u&J7`~iM=Uyopu7J4?yVmpZVD_tqWCU8N z0LbEQwL8Aj;PHcsKVHAMwDdFrWSw-ykbtFILn;*t4fMmMD<%KaIyvU824A-3+&WYa z)Y22!Z(vZ{Lg*O!leWGy3V?xVt1_Nmte42hGtyRgk!{vk;R|eSmDZ^N=GH)04DVXx zPwBZc*Kvnq*b%6G)j%BFYPgp{ap}TN+z~kupkItV=Lz9XRh$+DFhp(rLZ<)W#X znp+V0-p_Alo=hj=-?b);7e2&g8 zd%aYO+J<#LaS_VMq;a1?!Y$Ez2UlBr8sZ^Eg)BIZ&57Z zYuh^*cdogY@V0Y4?un*XJm!ZLE-i`L^Fp#}q$JWxXhLO{{sM|}X7!#_t=U%W_nh8* zs&?A~)USL!I}i>H0}A{e83-yf^oEo1cwVOA}CtHaSqM}hCfnO2Mnv0m}x4pl0`G#g_gh6rRVKbg;h z;>t?2OVsrqTK~p3s`n-P>`}$^2{iR6AK%9F!e^8Og+j~ooS**nzh{m8?_2F!v%R(t z+StE-DOT1a&)7YlPffg;>km-6oS{%INbmhej~rSZ$$UvMpJnbsdih8`U~BFL-GrHp z1D~7{sl+2;{6uoL>FmxOIK}{GHnF;yp$u&-!11m!e{~=cvazEDXJc=1aNU$>M}f zMf}1a;%hMP2~SetsIL$ue|H$V0FZ5ntZK&d@G2Bv@d8r)^maUkb)*fits2b*y%zr< zkP;_Bqf}M`0g=lDfs5;!;u4LFlQ;e26Yjukh!zw5UzI}p(i*op`mAwy&}qG^HR}hR!^Z2@%ab>6{zcd5uw$w4bSzTf9RtO9 zUc5bPXm;{KVV_u2Q6@4~DtF-G!dNbl*d_z;IWm%0xv1cUiRo=TfcQW~S>eT&lzeD{M)ghm=r7-!_!cbw1)Q^6qLAIDwOvfDyuU(Y}vuCjxjbejQ-SlPiLF3H<^RRBn{1tWl0&H#^!&qN2JJA=7WvQ2@!*>r zL+C*8r%(PzKkxPmisC5jV{{cv7>zRCyvwCcY&r?>uDro`!gnIy`m=ZQb36?EDF!TJgLD{A^X^dD3cs$`#5NB7E@sCqB++re?|WJn=htIAQEiDC;s^+3NA9Cx zZN$mq5zqhhufxWE{kT={R684$+4YRp`pC0V7|2$w^-J0RN9{k6c9+sdhW%%Adv7yp z|KHl#eX#%B$H&g9ptotf3cqUe3)F9_d$UVa?Kidxg;Uf90~r|!W%)U|9gZl6dLMJr z!gR!YLl_hpH`k*Zkv)l`-A^yvk+cGg&%;E+nDkqvRFrs8Fsm-*D;QCtP}9?=iviF0 z2%lpTh;GpN-HPnpN=aNnDU;6&T|z9&RLb;)C%#uMaQZ6|%7V_^hd%EqBrx%6EYlXj zyqx-1983VUIO)k;h*vpzO8jgn4o^#elA;H~QVsgRZ4hlxT&HKjK<vP=G?G zPylvRZ|=W?pS9PG!$$kZa^X#*eT*>QoHU)9a|-*(#{Su1t?8VeHBV1k^|I4$v=8fm z#Q{J+UTYk`X+mrDqxx|h)a+xt{8)bvKMJ5dA0ASZ`zNPAHXCo>wVih-hX?g0VvOB5 zIj&a)001F58+9AMd zVP(DWl9u1E{)w*t`($=K9A7N4jr8?@dvkYZCwl(3yYq1Vb1$Dqu$pgft!==6n{v0r z=RB~bU3Hr5HbLGh(H>jgUOQy)3vIF#>Hd5%l3L7iX7tj$av}tLkZr~>}cJ8kuQx7S4ug+>u_I9zA zy(iELQ+;D?V|Q(1M^SyNvZb-fzfqqRHhg9a0wOwtCCDBoZhPSvq3xfzfUd!-t1Yp* zEzZ6~7pkXTze7Z(yM~wZ(MSFXEOQ0##85!x-JP&ufmJ}yt0r~XIu_gnp!DJUt*6*) zIlQtIn>XS0=2P>9$`wX0pOkcALHWNvv8O1%-$U3tRibM(Ym_&U>z`~<>8&mLOHa0G zTy{6g&Xc`uF(ax^*Q%TP6yBxh<-I23TUtY*Cz~QoLzA+}DY+ z_jEHHrH!4n%{^n3HVUzR%F|A$`(!I*V|4t+t0Eei_MTM3-k=#1T1B^TIo{C5J{8-@ zi?6J)w;KXhi~ukHwe~}plJ;Lz5;zh5B60VgSVnE)iqNytYk$O2+3WG45zgPvl}{FA zgtxnG;`&MWXluRRp~m(!o>Zs4(tQqvLKoWWiU`d-%2-!J5yURu?BaKt_@d1Xmetk) z^u{0c(55bGp?9>5iy?@%y=TD)Q&Yp=Ih{Xa^hv&Ls3rErnp=`bU||C`d8+Y96=t$x z0uDZaKIbAxgA|;crxrkMX(Ok}M1l3t9l@eI6d}lfooQ-pZRv<#b!EJ{u2Bd@(caeo zzrAaHYU{Y-{p`O&HJwWb(gv&+qKs zJ$r?OoTi>>D}9hZ3%LB+T(Iul#b43V00*X1Gnn>7Izs_C!H<@_h&QatEn%@sg1AHg?& zh)AAZpJZ1%sjO83N>qjM*d?r&VHP3Lmkl16uph_JXR4vT&4a`5&hbiGy6V^>JN3HZ zKn8nz{r84IjQ94KUUm8g0tv!Q>kjye3Q8~D-m;OUeuzIwIR>OzIk=KYa`TVtY{iwG9~nX+!z7yXRZyqQMErtTUg04%YASyFnA zD%gy;{UK)w_f=vB%4h|&&H8a$AtZC;bqRQ6iNUp5+7e(^d%-ua`@CBuwuZXYUOF%^ zINLaVgtg3o$uqE%Swirifud%a#&>f$kDJ92niTBfWe-uR;B+#{?Vq;WwO0hY0{J%3 ze*@z-c<-7H4jYi^k^&A5|5{H!>11^xWep-rJ=2rm{=Jp)CxayD_Gb=-EIS7k@KvSa`KA`_x|+52YT$1ys&~jwvkL?g0k)&&CsFV$S}hT!Euf_c z5KW|8tTAc?Yt&DkJPZV>6H>jN!4PkYF`4m5%a^gVMQKLUNWp`rxL#=ke`zUv>(z_E zRZJUA`fg_U{&4b^3|pq`ZO zB#QbtKG!q2ZVOczF9;A3=sZnV%kL?WWSW*#07h6q1wrIoqE?xhd!Sd-%)dTDTEQ|p zk4BfWoVihjpzK_%X=epifwSWh)DZ(+ zB%(EWEq2U)-{gI&$kHuXNkqw;S2R`>Kc=Bfb+(-JrIJpEJ3X9qM#B%Y4#jvwFHqg~ zQigd`$+`BXkS%U+uRq#CW7g@M8Va)Q1Yq{FHV-2)KDlWUnjk}H4*}$+Mbau=aeEz| z_BVkhDUE=1qZC^buS6Y53#}rc*a+wra!0E1J8rs@V8oFT715GXuEUe_*aH-`q?9F7 zFFaBikbdFRNte;g2@2k%5eO=SLO<=s9$$Kf*517Rw?|X%)6251x#&NQ!=4Kv@2HJD zWN!{%?;m{MIXFG}_VDbyUgn#PvKO$lqlUV-V7u&&nR5%}g@7if- zM)@`OXMS{MY$!(#X*ch8ZJP4|&H3c$4fU8LVUL3U5Jrqb5C~L0Nt~>OpwEVe`1ZYW z-kpLgYE&A;$^rEUnwu^BkwQa|(}dQH5<8bmd!Pk2*Fe(j{9sjXPTaI)wQlBr-6Puq zE5O1nkjfZ|smf?FX*L2zaQEQNQJ}IQ%L>_FsHqC{Q=GDah`#r|EyB@#aY6%A()f`y z1f~g*qR)m)%>*hrsI|cYAe=%lJV}Oo)(l97kkCbvC~7Qv>=m+xxQzY|qPakHh+Xn! z7u`G1%{?_h{ghzxDQ(qLT!-dbWFmpUoHP`^V=M&=YKftX{OQzQP}2E`kjco^p?84B z5ftM)HmNy5B$4V;&4HVD@CCq>WSMIR4!!32 z>W_cSDple}6i7YsF^R@LOBE_M4j0Xr<8wNNM2nrcpHQUOd!Jb`gfcA_yG-AkXu-JL zX+WHSY9*Op}s+4cM`Ho2~L_H9sx&kOHq*boQ z(4GVph&uASrx0I-27`_r`R1v;Pu4iWxF>PB3+6uXo()oZ!(7X%3H=mr}dIQ4% zW3@Wek6_8BFy|VHHK849hTf+Hi?b+^dawr`g6-urrg>yN=M9`|mYAO<>v)f)X+I;22OC}gM3 z{er-OGZcMg%U$|9z$&xL){lQqsIw)41q6h3O2zIkugJ%@_R}4=S1wK zUf&*sh26na^<@AJ%*EjWs0&y0G5F)sQ*5+1Tz-9YBDF^3#rwUPrA|UA+neO!D{mS= zZ}_Odf*Ewo{&UfcTl!~V3~)W9Z=onU(!9a-zEBp%i7?KT5OOEK?2N`U;xpjV6{n3w zmeHEPcz$RkWeG5Xj@vS)aNPd(fDXsBgFP&kyz&a}J%+bXoYJ+5Glg$&mY~i7-u1V# z>Lvt4=Q1Y6qFDO(oVLGXx@wS1-{6`8;=t4bs$gM?GpQV&Pf3!ZIOKvkmaZLf9F?vK zm-y?DbXR~7$1&n>;#Dn91Q8ij%f!X?=Is9!YvCQh-Ry=@mX)bl6nVX06}Dru?vg%h zMCf5c$?<$fCwPJ2+~|cRn@l42F!YU8ZW%%=Er(~XirXhGSlK~^;QiZ~Y6%g)j&!=Rm@ z)@TS#{nST_ViBwdi5{h6C$^$O@1Y-618H{Swr7fHIJva9@BEXZ>rO|)t)o05f9 zCz)N|4@i|2>@GYU=muF22j174A$65cd0PN!l}bC88eignB1EzpOzvXj#}`9e@6wib zb)o>W2CxM8jjNxYMIRQ!O(fSMfDHt=td*G?@iIm1?6fdGsA2+ zs;_S-r;~n!Yk@BxvsD-4@%0O#Y^8%qo=TXIe>J$AlRVcNZX)8LFGGwecy4s8E%PT#SAOE@HgUM98Xj?VxX!N&m{AQ=))OmFz_^>=t+rk zDtH^In9#9G5e4F~yge(=K7p%Zr@b1CrH-HDDahp>vkSB>x>LB;i59|m;KEbb>TZw0 zIw%r?ENP^HAD)@}@Qm=ov&$@bMjf{TOOaPs3^@-KT&oRn8TWGz=oI?{bqs6$nMF`K zpfpg-S}2l{6zmYZz9x^9c%mCKPAn)YGY`Yj{APi*(~i$PHU};xzy}3C{-YK0D7q)| zFF*TxLb%;HuS1CnQhf&Rg?aG6k_2v>f(26_dH*jqH-!uAh4N0r#_ZER1l z7X+EW4q2!UaEbB_Yh6Q=}Du%dC#y(%l>|A2^DqHZVg)bgFK;Sr{BK{^ROu-FA!M9T>{6#B# zYkcD=?0^d5vWodP&Mv`T!+}@96#g>82_yz+@PxI36-2>K1+ znLp*%^9j3g!GA|OGf<1&!H`=P5iaEXM3WHg`ZV-(!1ZxH0B*UIt}^fEvjwZZb3G#! z39%=;gYldr)Y4=rjBf!{phCvcI)#H9jiYl3`Ow4Arf@dL=86hgB#4d2Gea_gv6`89 ziEckt7J6PO;xZkp{rJs+niG0(4lP4mCZ>dlNCKKxWML+z=tms~Fw?X!17^?KG6<#f z)fP$1DhoF}*beuePld=3;q(TaxP{)}m7oZSbK@@DHQo=eDdh{WZ;0igYm4;-j+ga9 ztmB8Se%;-|#b;FlD}14hfeH%h7U(nRs|i&FeL-sjD4-US?L{#S3omuWT<=LuCqeI-?R_iJb_9m? z8WQXXzxf4^LK`jvp282H%LAl#fFW@UBajv0Ghw#mJ|7wW{6}4PLCj*cZqU1a-GJ2$ zO8ELkkLzEEcEC6OI`>c-oUxCcT0}dmTfx*4J2fk^$l(nBZPxGlqyu3;k9_3PuvlB2 z1WmXMnBSe;8a2XE8^Nft9kn+Y$X9=!@BCt3|GBxNvK<3mS@MhRs%RW!d0qBS!IAs- z_`tqwgZ+L%Q4DLp&kL09s$yx4nhMWa+okcUCcS%j?NzsT3b&)LzVg3+FMfXspe>w} zAiRz!ewJht^jTDwFLLg^w?Y2<2c6S*=WpMgvo)HIs=iyeO~~m+SukCv?vM_%KC-hz z*a_HV@ey5)g;%Fb-kYv^DR9sYUgIbDJKidLi?R~iY{xhs6x}+Lzb#|&^G@e@|1YO! z=AWaJ)3aZky`EAwLdj*L@$9ZF?gszebaT`D=XhfN$qi>9fW-IY)B>_AaSV<-pH^x$ z>%da$fB4{RL8GzAsY!x^5x)J&CFPsz6guSqN$bhaYT7})*pAb$|8jV64z~#Ku=kbd zLu7q-PRwyldzV8IG$GRSPG~0e1&bvATLpMl$R8Hq>mE8__s@L?#YL!c^PNiAs1&lv zAk7ljw6tE^!*d#V2eQ)h%1_)2ZJ|M4T)Ud8a0$&Ur&oRIQh_`zvaJ3mQVx~#|C}8D z?YAxd@w;DB|3jzgrvA@o`7GprI(_^7(aG!Ibc{#qKdGo(|3j%{bN-*>b5JA-tR8Gr zW=F#hMJlbldUy8joBj4TN0oMO(EB(iQ%$8(d0FI0{M3A>^mdhOLqQrx@NT7?qo_|16(P{hv+!pH2Or zP5mDs2yg2DZ0i4P>i=x&|7`02Z0i4P>i=x&|7`02Z0i4P>i_(o)c^7SXVZ|y0Qu% zvPxA?I9-LH%3QEy@qrzfOmD9Dr+@hQ;CTP9hn<6WXJ>~e=Y|7lpLbp!o&6%NfL$ox rfv4L~C>r;&x=}W7KAX?xv-xa3o6qL6`D{M_wV!_hz@(&X02l@UdPqY&