From 11a46269f13a405bda5b42fb9ba3d364417e1932 Mon Sep 17 00:00:00 2001 From: wangmenglan Date: Tue, 18 Apr 2023 16:03:57 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B0=86kni=E5=90=88=E5=B9=B6=E5=88=B0tfe?= =?UTF-8?q?=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 8 + cache/src/tango_cache_client.cpp | 32 +- common/CMakeLists.txt | 10 +- common/include/tfe_acceptor_kni.h | 152 ++ common/include/tfe_addr_tuple4.h | 66 + common/include/tfe_ctrl_packet.h | 44 + common/include/tfe_metrics.h | 61 + common/include/tfe_mpack.h | 17 + common/include/tfe_packet_io.h | 23 + common/include/tfe_raw_packet.h | 99 ++ common/include/tfe_session_table.h | 58 + common/include/tfe_tap_rss.h | 35 + common/include/tfe_timestamp.h | 24 + common/include/tfe_utils.h | 69 +- common/include/uthash.h | 1316 ++++++++++++++++ common/src/tfe_acceptor_kni.cpp | 115 ++ common/src/tfe_addr_tuple4.cpp | 57 + common/src/tfe_ctrl_packet.cpp | 142 ++ common/src/tfe_metrics.cpp | 133 ++ common/src/tfe_mpack.cpp | 216 +++ common/src/tfe_packet_io.cpp | 1553 +++++++++++++++++++ common/src/tfe_raw_packet.cpp | 994 ++++++++++++ common/src/tfe_session_table.cpp | 219 +++ common/src/tfe_tap_rss.cpp | 389 +++++ common/src/tfe_timestamp.cpp | 65 + common/src/tfe_utils.cpp | 177 ++- conf/tfe/tfe.conf | 53 +- platform/CMakeLists.txt | 3 +- platform/include/internal/acceptor_kni_v4.h | 13 + platform/include/internal/proxy.h | 2 + platform/src/acceptor_kni_v4.cpp | 152 ++ platform/src/proxy.cpp | 14 +- vendor/CMakeLists.txt | 16 + vendor/msgpack-c-6.0.0.tar.gz | Bin 0 -> 68965 bytes 34 files changed, 6301 insertions(+), 26 deletions(-) create mode 100644 common/include/tfe_acceptor_kni.h create mode 100644 common/include/tfe_addr_tuple4.h create mode 100644 common/include/tfe_ctrl_packet.h create mode 100644 common/include/tfe_metrics.h create mode 100644 common/include/tfe_mpack.h create mode 100644 common/include/tfe_packet_io.h create mode 100644 common/include/tfe_raw_packet.h create mode 100644 common/include/tfe_session_table.h create mode 100644 common/include/tfe_tap_rss.h create mode 100644 common/include/tfe_timestamp.h create mode 100644 common/include/uthash.h create mode 100644 common/src/tfe_acceptor_kni.cpp create mode 100644 common/src/tfe_addr_tuple4.cpp create mode 100644 common/src/tfe_ctrl_packet.cpp create mode 100644 common/src/tfe_metrics.cpp create mode 100644 common/src/tfe_mpack.cpp create mode 100644 common/src/tfe_packet_io.cpp create mode 100644 common/src/tfe_raw_packet.cpp create mode 100644 common/src/tfe_session_table.cpp create mode 100644 common/src/tfe_tap_rss.cpp create mode 100644 common/src/tfe_timestamp.cpp create mode 100644 platform/include/internal/acceptor_kni_v4.h create mode 100644 platform/src/acceptor_kni_v4.cpp create mode 100644 vendor/msgpack-c-6.0.0.tar.gz diff --git a/CMakeLists.txt b/CMakeLists.txt index b467b2d..458de43 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,14 @@ add_definitions(-D_GNU_SOURCE) set(CMAKE_CXX_STANDARD 11) set(CMAKE_C_STANDARD 11) +find_package(LIBBPF) +if (SUPPORT_BPF) + add_definitions(-DSUPPORT_BPF=1) + message(STATUS "Support BPF") +else() + message(STATUS "Not support BPF") +endif() + if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE RelWithDebInfo) endif() diff --git a/cache/src/tango_cache_client.cpp b/cache/src/tango_cache_client.cpp index cdfb1bc..80a462d 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ʹctxevbuffer޷ctxȡ +//�����ϴ�API��ʹ��ctx��evbuffer�������޷�����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:");//עPOSTExpectϵҪȷCURLOPT_POSTFIELDSIZE - //ͷGETʱԭ + ctx->headers = curl_slist_append(ctx->headers, "Expect:");//ע��POST������Expect��ϵ��Ҫ��ȷ����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 4bb1b70..8c98f04 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -1,11 +1,15 @@ add_library( common src/tfe_utils.cpp src/tfe_types.cpp src/tfe_future.cpp src/tfe_http.cpp src/tfe_plugin.cpp src/tfe_rpc.cpp src/tfe_cmsg.cpp src/tfe_kafka_logger.cpp src/tfe_resource.cpp src/tfe_scan.cpp - src/tfe_pkt_util.cpp src/tfe_tcp_restore.cpp src/raw_socket.cpp src/packet_construct.cpp src/tfe_fieldstat.cpp - src/tap.cpp src/io_uring.cpp src/intercept_policy.cpp) + src/tfe_pkt_util.cpp src/tfe_tcp_restore.cpp src/raw_socket.cpp src/packet_construct.cpp + src/tap.cpp src/io_uring.cpp src/intercept_policy.cpp src/tfe_fieldstat.cpp + src/tfe_addr_tuple4.cpp src/tfe_packet_io.cpp src/tfe_session_table.cpp src/tfe_timestamp.cpp + src/tfe_acceptor_kni.cpp src/tfe_ctrl_packet.cpp src/tfe_raw_packet.cpp + src/tfe_mpack.cpp src/mpack.cpp src/tfe_tap_rss.cpp src/tfe_metrics.cpp) 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) +target_link_libraries(common PUBLIC MESA_handle_logger cjson msgpack) 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 new file mode 100644 index 0000000..7bb3fb3 --- /dev/null +++ b/common/include/tfe_acceptor_kni.h @@ -0,0 +1,152 @@ +#ifndef _TFE_ACCEPTOR_KNI_H +#define _TFE_ACCEPTOR_KNI_H + +#ifdef __cpluscplus +extern "C" +{ +#endif + +#include + +// #include "proxy.h" +#include "tfe_utils.h" +#include "tfe_timestamp.h" +#include "tfe_packet_io.h" +#include "tfe_session_table.h" + + /****************************************************************************** + * Struct For tap + ******************************************************************************/ + + struct tap_config + { + int enable_iouring; + int enable_debuglog; + + int ring_size; + int buff_size; + + int flags; + int sq_thread_idle; + + char src_mac[6]; + char tap_mac[6]; + char tap_c_mac[6]; + char tap_s_mac[6]; + char tap_device[16]; + char tap_c_device[16]; + char tap_s_device[16]; + int tap_rps_enable; + char tap_rps_mask[TFE_SYMBOL_MAX]; + + struct bpf_ctx *tap_bpf_ctx; + }; + + struct tap_ctx + { + int tap_s; + int tap_c; + int tap_fd; + + struct io_uring_instance *io_uring_fd; + struct io_uring_instance *io_uring_c; + struct io_uring_instance *io_uring_s; + + int buff_size; + char *buff; + }; + + /****************************************************************************** + * Struct For Thread + ******************************************************************************/ + struct acceptor_thread_ctx + { + pthread_t tid; + int thread_index; + + struct tap_ctx *tap_ctx; + struct session_table *session_table; + struct sf_metrics *sf_metrics; + + struct tap_config *ref_tap_config; + struct packet_io *ref_io; + struct global_metrics *ref_metrics; + struct policy_enforcer *ref_enforcer; + struct acceptor_ctx *ref_acceptor_ctx; + struct tfe_proxy *ref_proxy; + + int session_table_need_reset; + }; + + /****************************************************************************** + * Struct For Session + ******************************************************************************/ + + struct packet_info + { + int dir_is_e2i; + 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; + + uint16_t user_field; + + struct route_ctx raw_pkt_i2e_route_ctx; + struct route_ctx raw_pkt_e2i_route_ctx; + + struct sids raw_pkt_i2e_sids; + struct sids raw_pkt_e2i_sids; + + // depending on first control packet + struct packet_info first_ctrl_pkt; + + // 加锁 + struct tfe_cmsg *cmsg; + struct acceptor_thread_ctx *ref_thread_ctx; + }; + + struct session_ctx *session_ctx_new(); + void session_ctx_free(struct session_ctx *ctx); + + /****************************************************************************** + * Struct For KNI + ******************************************************************************/ + + struct acceptor_ctx + { + int firewall_sids; + int sce_sids; + int nr_worker_threads; + + int cpu_affinity_mask[TFE_THREAD_MAX]; + + 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; + }; + + struct acceptor_ctx *acceptor_ctx_create(const char *profile); + void acceptor_ctx_destory(struct acceptor_ctx *ctx); + +#ifdef __cpluscplus +} +#endif + +#endif diff --git a/common/include/tfe_addr_tuple4.h b/common/include/tfe_addr_tuple4.h new file mode 100644 index 0000000..81aab88 --- /dev/null +++ b/common/include/tfe_addr_tuple4.h @@ -0,0 +1,66 @@ +#ifndef _TFE_ADDR_TUPLE4_H +#define _TFE_ADDR_TUPLE4_H + +#ifdef __cpluscplus +extern "C" +{ +#endif + +#include + + enum addr_tuple4_type + { + ADDR_TUPLE4_TYPE_V4, + ADDR_TUPLE4_TYPE_V6, + }; + + struct addr_v4 + { + struct in_addr src_addr; /* network order */ + struct in_addr dst_addr; /* network order */ + }; + + struct addr_v6 + { + struct in6_addr src_addr; /* network order */ + struct in6_addr dst_addr; /* network order */ + }; + + struct addr_tuple4 + { + enum addr_tuple4_type addr_type; + in_port_t src_port; /* network order */ + in_port_t dst_port; /* network order */ + union + { + struct addr_v4 addr_v4; + struct addr_v6 addr_v6; + }; + }; + +#define INIT_ADDR_V4(name, src_addr_str, src_port_num, dst_addr_str, dst_port_num) \ + struct addr_tuple4 name; \ + memset(&name, 0, sizeof(name)); \ + (name).addr_type = ADDR_TUPLE4_TYPE_V4; \ + (name).src_port = htons((src_port_num)); \ + (name).dst_port = htons((dst_port_num)); \ + inet_pton(AF_INET, (src_addr_str), &(name).addr_v4.src_addr); \ + inet_pton(AF_INET, (dst_addr_str), &(name).addr_v4.dst_addr); + +#define INIT_ADDR_V6(name, src_addr_str, src_port_num, dst_addr_str, dst_port_num) \ + struct addr_tuple4 name; \ + memset(&name, 0, sizeof(name)); \ + (name).addr_type = ADDR_TUPLE4_TYPE_V6; \ + (name).src_port = htons((src_port_num)); \ + (name).dst_port = htons((dst_port_num)); \ + inet_pton(AF_INET6, (src_addr_str), &(name).addr_v6.src_addr); \ + inet_pton(AF_INET6, (dst_addr_str), &(name).addr_v6.dst_addr); + + char *addr_tuple4_to_str(const struct addr_tuple4 *addr); + void addr_tuple4_reverse(const struct addr_tuple4 *orin, struct addr_tuple4 *out); + +#ifdef __cpluscplus +} +#endif + +#endif diff --git a/common/include/tfe_ctrl_packet.h b/common/include/tfe_ctrl_packet.h new file mode 100644 index 0000000..2ae43db --- /dev/null +++ b/common/include/tfe_ctrl_packet.h @@ -0,0 +1,44 @@ +#ifndef _TFE_CTRL_PACKET_H +#define _TFE_CTRL_PACKET_H + +#ifdef __cpluscplus +extern "C" +{ +#endif + +#include + +enum session_state +{ + SESSION_STATE_OPENING = 1, + SESSION_STATE_CLOSING = 2, + SESSION_STATE_ACTIVE = 3, + SESSION_STATE_RESETALL = 4, +}; + +struct ctrl_pkt_parser +{ + char tsync[4]; + uint64_t session_id; + enum session_state state; + char method[32]; + uint64_t tfe_policy_ids[32]; + int tfe_policy_id_num; + uint64_t sce_policy_ids[32]; + int sce_policy_id_num; + struct tfe_cmsg *cmsg; +}; + +const char *session_state_to_string(enum session_state state); +void ctrl_packet_parser_init(struct ctrl_pkt_parser *handler); + +// return 0 : success +// return -1 : error +int ctrl_packet_parser_parse(struct ctrl_pkt_parser *handler, const char *data, size_t length); +void ctrl_packet_parser_dump(struct ctrl_pkt_parser *handler); + +#ifdef __cpluscplus +} +#endif + +#endif diff --git a/common/include/tfe_metrics.h b/common/include/tfe_metrics.h new file mode 100644 index 0000000..32c76e7 --- /dev/null +++ b/common/include/tfe_metrics.h @@ -0,0 +1,61 @@ +#ifndef _GLOBAL_METRICS_H +#define _GLOBAL_METRICS_H + +#ifdef __cpluscplus +extern "C" +{ +#endif + +#include "tfe_utils.h" +#include + +struct global_metrics_config +{ + char output_file[256]; + char statsd_server[32]; + int statsd_port; + int statsd_format; + int statsd_cycle; + + int prometheus_listen_port; + char prometheus_listen_url[256]; +}; + +struct global_metrics +{ + struct throughput_metrics raw_pkt_rx; // 累计值 + struct throughput_metrics raw_pkt_tx; // 累计值 + + struct throughput_metrics hit_policy; // 累计值 + + struct throughput_metrics decrypt_tx; // 累计值 + struct throughput_metrics decrypt_rx; // 累计值 + + struct throughput_metrics ctrl_pkt_rx; // 累计值 + + uint64_t ctrl_pkt_opening_num; // 累计值 + uint64_t ctrl_pkt_active_num; // 累计值 + uint64_t ctrl_pkt_closing_num; // 累计值 + uint64_t ctrl_pkt_resetall_num; // 累计值 + uint64_t ctrl_pkt_error_num; // 累计值 + + uint64_t sf_active_times; // 累计值 + uint64_t sf_inactive_times; // 累计值 + + uint64_t session_nums; // 瞬时值 + uint64_t send_log; // 瞬时值 + + struct global_metrics_config config; + screen_stat_handle_t fs_handle; + int fs_id[128]; +}; + +struct global_metrics *global_metrics_create(); +void global_metrics_destory(struct global_metrics *metrics); +void global_metrics_dump(struct global_metrics *metrics); + +#ifdef __cpluscplus +} +#endif + +#endif diff --git a/common/include/tfe_mpack.h b/common/include/tfe_mpack.h new file mode 100644 index 0000000..299cbbf --- /dev/null +++ b/common/include/tfe_mpack.h @@ -0,0 +1,17 @@ +#ifndef _TFE_MPACK_H +#define _TFE_MPACK_H + +#ifdef __cpluscplus +extern "C" +{ +#endif + +#include "tfe_cmsg.h" + +int parse_messagepack(const char* data, size_t length, void *ctx); + +#ifdef __cpluscplus +} +#endif + +#endif \ No newline at end of file diff --git a/common/include/tfe_packet_io.h b/common/include/tfe_packet_io.h new file mode 100644 index 0000000..5f1a6d8 --- /dev/null +++ b/common/include/tfe_packet_io.h @@ -0,0 +1,23 @@ +#ifndef _TFE_PACKET_IO_H +#define _TFE_PACKET_IO_H + +#ifdef __cpluscplus +extern "C" +{ +#endif + +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_thread_init(struct packet_io *handle, struct acceptor_thread_ctx *thread_ctx); +void packet_io_thread_wait(struct packet_io *handle, struct acceptor_thread_ctx *thread_ctx, int timeout_ms); + +int packet_io_polling_nf_interface(struct packet_io *handle, int thread_seq, void *ctx); +void handle_raw_packet_from_tap(const char *data, int len, void *args); +void handle_decryption_packet_from_tap(const char *data, int len, void *args); + +#ifdef __cpluscplus +} +#endif + +#endif diff --git a/common/include/tfe_raw_packet.h b/common/include/tfe_raw_packet.h new file mode 100644 index 0000000..a360b71 --- /dev/null +++ b/common/include/tfe_raw_packet.h @@ -0,0 +1,99 @@ +#ifndef _TFE_RAW_PACKET_H +#define _TFE_RAW_PACKET_H + +#ifdef __cpluscplus +extern "C" +{ +#endif + +#include + +enum layer_type +{ + // 数据链路层 + LAYER_TYPE_ETHER = 1 << 0, + LAYER_TYPE_PPP = 1 << 1, + LAYER_TYPE_HDLC = 1 << 2, + LAYER_TYPE_L2 = (LAYER_TYPE_ETHER | LAYER_TYPE_PPP | LAYER_TYPE_HDLC), + + // 数据链路层 -- 隧道 + LAYER_TYPE_VLAN = 1 << 3, + LAYER_TYPE_PPPOE = 1 << 4, + LAYER_TYPE_MPLS = 1 << 5, + LAYER_TYPE_L2_TUN = (LAYER_TYPE_VLAN | LAYER_TYPE_PPPOE | LAYER_TYPE_MPLS), + + // 网络层 + LAYER_TYPE_IPV4 = 1 << 6, + LAYER_TYPE_IPV6 = 1 << 7, + LAYER_TYPE_L3 = (LAYER_TYPE_IPV4 | LAYER_TYPE_IPV6), + + // 网络层 -- 隧道 + + // 传输层 + LAYER_TYPE_UDP = 1 << 8, + LAYER_TYPE_TCP = 1 << 9, + LAYER_TYPE_L4 = (LAYER_TYPE_UDP | LAYER_TYPE_TCP), + + // 传输层 -- 隧道 + LAYER_TYPE_G_VXLAN = 1 << 10, + LAYER_TYPE_GTPV1_U = 1 << 11, + + // ALL + LAYER_TYPE_ALL = (LAYER_TYPE_L2 | LAYER_TYPE_L2_TUN | LAYER_TYPE_L3 | LAYER_TYPE_L4 | LAYER_TYPE_G_VXLAN | LAYER_TYPE_GTPV1_U), + + // UNKNOWN + LAYER_TYPE_UNKNOWN, +}; + +enum ldbc_method +{ + LDBC_METHOD_HASH_INT_IP = 1, + LDBC_METHOD_HASH_EXT_IP = 2, + LDBC_METHOD_HASH_INT_IP_AND_EXT_IP = 3, + LDBC_METHOD_HASH_INNERMOST_INT_IP = 4, + LDBC_METHOD_HASH_INNERMOST_EXT_IP = 5, +}; + +struct layer_result +{ + uint16_t offset; + enum layer_type type; +}; + +struct layer_results +{ + struct layer_result layers[16]; + uint16_t layers_used; + uint16_t layers_size; +}; + +struct raw_pkt_parser +{ + enum layer_type expect_type; + struct layer_results results; + + const void *ptr_pkt_start; + uint64_t pkt_trace_id; +}; + +void raw_packet_parser_init(struct raw_pkt_parser *handler, uint64_t pkt_trace_id, enum layer_type expect_type, uint16_t expect_results_num); +// return most inner payload +const void *raw_packet_parser_parse(struct raw_pkt_parser *handler, const void *data, size_t length); + +// return 0 : success +// return -1 : error +int raw_packet_parser_get_most_inner_tuple4(struct raw_pkt_parser *handler, struct addr_tuple4 *addr); +int raw_packet_parser_get_most_outer_tuple4(struct raw_pkt_parser *handler, struct addr_tuple4 *addr); + +// return 0 : success +// return -1 : error +int raw_packet_parser_get_most_inner_address(struct raw_pkt_parser *handler, struct addr_tuple4 *addr); +int raw_packet_parser_get_most_outer_address(struct raw_pkt_parser *handler, struct addr_tuple4 *addr); + +uint64_t raw_packet_parser_get_hash_value(struct raw_pkt_parser *handler, enum ldbc_method method, int dir_is_internal); + +#ifdef __cpluscplus +} +#endif + +#endif diff --git a/common/include/tfe_session_table.h b/common/include/tfe_session_table.h new file mode 100644 index 0000000..9478915 --- /dev/null +++ b/common/include/tfe_session_table.h @@ -0,0 +1,58 @@ +#ifndef _SESSION_TABLE_H +#define _SESSION_TABLE_H + +#ifdef __cpluscplus +extern "C" +{ +#endif + +#include +#include + +#include "uthash.h" +#include "tfe_addr_tuple4.h" + +// Note: session_addr must be initialized by memset(0) before use !!! + +typedef void fn_free_cb(void *args); + +struct session_node +{ + uint64_t session_id; /* first key */ + struct addr_tuple4 session_addr; /* second key */ + + void *val_data; + fn_free_cb *val_freecb; + + UT_hash_handle hh1; /* handle for first hash table */ + UT_hash_handle hh2; /* handle for second hash table */ +}; + +struct session_table; + +struct session_table *session_table_create(); +void session_table_destory(struct session_table *table); +void session_table_reset(struct session_table *table); +uint64_t session_table_count(struct session_table *table); + +// session_addr : deep copy +// val_data : shallow copy (malloc by user, free by val_freecb) +// return 0 : suceess +// return -1 : key exists +int session_table_insert(struct session_table *table, uint64_t session_id, const struct addr_tuple4 *session_addr, void *val_data, const fn_free_cb *val_freecb); + +// return 0 : success +// return -1 : key not exists +int session_table_delete_by_id(struct session_table *table, uint64_t session_id); +int session_table_delete_by_addr(struct session_table *table, const struct addr_tuple4 *session_addr); + +// return NULL : key not exists +// return UnNULL : success +struct session_node *session_table_search_by_id(struct session_table *table, uint64_t session_id); +struct session_node *session_table_search_by_addr(struct session_table *table, const struct addr_tuple4 *session_addr); + +#ifdef __cpluscplus +} +#endif + +#endif diff --git a/common/include/tfe_tap_rss.h b/common/include/tfe_tap_rss.h new file mode 100644 index 0000000..3ae0872 --- /dev/null +++ b/common/include/tfe_tap_rss.h @@ -0,0 +1,35 @@ +#ifndef _TFE_TAP_RSS_H_ +#define _TFE_TAP_RSS_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define TAP_RSS_LOG_TAG "TAP_RSS: " + +struct bpf_ctx; + +int tfe_tap_get_bpf_prog_fd(struct bpf_ctx *ctx); + +struct bpf_ctx *tfe_tap_global_load_rss_bpf(const char *bpf_obj_file, uint32_t bpf_queue_num, uint32_t bpf_hash_mode, uint32_t bpf_debug_log, void *logger); +void tfe_tap_global_unload_rss_bpf(struct bpf_ctx *ctx); + +struct tap_ctx *tfe_tap_ctx_create(void *ctx); + +struct tap_config *tfe_tap_config_create(const char *profile, int thread_num); +void tfe_tap_destory(struct tap_config *tap); + +int tfe_tap_set_rps(void *local_logger, const char *tap_name, int thread_num, const char *rps_mask); + +int tfe_tap_open_per_thread(const char *tap_dev, int tap_flags, int bpf_prog_fd, void *logger); +void tfe_tap_close_per_thread(int tap_fd); + +int tfe_tap_read_per_thread(int tap_fd, char *buff, int buff_size, void *logger); +int tfe_tap_write_per_thread(int tap_fd, const char *data, int data_len, void *logger); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/common/include/tfe_timestamp.h b/common/include/tfe_timestamp.h new file mode 100644 index 0000000..4266c87 --- /dev/null +++ b/common/include/tfe_timestamp.h @@ -0,0 +1,24 @@ +#ifndef _TFE_TIMESTAMP_H +#define _TFE_TIMESTAMP_H + +#ifdef __cpluscplus +extern "C" +{ +#endif + +#include + +struct timestamp *timestamp_new(uint64_t update_interval_ms); +void timestamp_free(struct timestamp *ts); + +void timestamp_update(struct timestamp *ts); +uint64_t timestamp_update_interval_ms(struct timestamp *ts); + +uint64_t timestamp_get_sec(struct timestamp *ts); +uint64_t timestamp_get_msec(struct timestamp *ts); + +#ifdef __cpluscplus +} +#endif + +#endif diff --git a/common/include/tfe_utils.h b/common/include/tfe_utils.h index 1b9af35..4081782 100644 --- a/common/include/tfe_utils.h +++ b/common/include/tfe_utils.h @@ -10,6 +10,18 @@ #include //scan_dir #include +#define LOG_TAG_POLICY "POLICY" +#define LOG_TAG_UTILS "UTILS" +#define LOG_TAG_RAWPKT "RAW_PACKET" +#define LOG_TAG_CTRLPKT "CTRL_PACKET" +#define LOG_TAG_STABLE "SESSION_TABLE" +#define LOG_TAG_PKTIO "PACKET_IO" +#define LOG_TAG_METRICS "G_METRICS" +#define LOG_TAG_SF_METRICS "SF_METRICS" +#define LOG_TAG_SF_STATUS "SF_STATUS" +#define LOG_TAG_SCE "SCE" +#define LOG_TAG_TIMESTAMP "TIMESTAMP" + #define TFE_STRING_MAX 2048 #define TFE_PATH_MAX 256 #define TFE_SYMBOL_MAX 64 @@ -170,4 +182,59 @@ int tfe_scandir(const char *dir, struct dirent ***namelist, char *tfe_read_file(const char *filename, size_t *filelen); const char * tfe_version(); -int tfe_decode_base64url(u_char *dst, u_char *src); \ No newline at end of file +int tfe_decode_base64url(u_char *dst, u_char *src); + +/****************************************************************************** + * sids + ******************************************************************************/ + +#include + +struct sids +{ + int num; + sid_t elems[MR_SID_LIST_MAXLEN]; +}; + +void sids_write_once(struct sids *dst, struct sids *src); +void sids_copy(struct sids *dst, struct sids *src); + +/****************************************************************************** + * route_ctx + ******************************************************************************/ + +struct route_ctx +{ + char data[64]; + int len; +}; + +int route_ctx_is_empty(struct route_ctx *ctx); +void route_ctx_copy(struct route_ctx *dst, struct route_ctx *src); + +/****************************************************************************** + * protocol + ******************************************************************************/ + +struct udp_hdr +{ + u_int16_t uh_sport; /* source port */ + u_int16_t uh_dport; /* destination port */ + u_int16_t uh_ulen; /* udp length */ + u_int16_t uh_sum; /* udp checksum */ +} __attribute__((__packed__)); + +void build_udp_header(const char *l3_hdr, int l3_hdr_len, struct udp_hdr *udp_hdr, u_int16_t udp_sport, u_int16_t udp_dport, int payload_len); +void build_ip_header(struct ip *ip_hdr, u_int8_t next_protocol, const char *src_addr, const char *dst_addr, uint16_t payload_len); +void build_ether_header(struct ethhdr *eth_hdr, uint16_t next_protocol, const char *src_mac, const char *dst_mac); + +int str_to_mac(const char *str, char *mac_buff); +int get_mac_by_device_name(const char *dev_name, char *mac_buff); + +struct throughput_metrics +{ + uint64_t n_pkts; + uint64_t n_bytes; +}; + +void throughput_metrics_inc(struct throughput_metrics *iterm, uint64_t n_pkts, uint64_t n_bytes); diff --git a/common/include/uthash.h b/common/include/uthash.h new file mode 100644 index 0000000..f6a2a4a --- /dev/null +++ b/common/include/uthash.h @@ -0,0 +1,1316 @@ +/* +Copyright (c) 2003-2021, Troy D. Hanson http://troydhanson.github.com/uthash/ +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef UTHASH_H +#define UTHASH_H + +#define UTHASH_VERSION 2.3.0 + +#include /* memcmp, memset, strlen */ +#include /* ptrdiff_t */ +#include /* exit */ + +#if defined(HASH_DEFINE_OWN_STDINT) && HASH_DEFINE_OWN_STDINT +/* This codepath is provided for backward compatibility, but I plan to remove it. */ +#warning "HASH_DEFINE_OWN_STDINT is deprecated; please use HASH_NO_STDINT instead" +typedef unsigned int uint32_t; +typedef unsigned char uint8_t; +#elif defined(HASH_NO_STDINT) && HASH_NO_STDINT +#else +#include /* uint8_t, uint32_t */ +#endif + +/* These macros use decltype or the earlier __typeof GNU extension. + As decltype is only available in newer compilers (VS2010 or gcc 4.3+ + when compiling c++ source) this code uses whatever method is needed + or, for VS2008 where neither is available, uses casting workarounds. */ +#if !defined(DECLTYPE) && !defined(NO_DECLTYPE) +#if defined(_MSC_VER) /* MS compiler */ +#if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ +#define DECLTYPE(x) (decltype(x)) +#else /* VS2008 or older (or VS2010 in C mode) */ +#define NO_DECLTYPE +#endif +#elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__) +#define NO_DECLTYPE +#else /* GNU, Sun and other compilers */ +#define DECLTYPE(x) (__typeof(x)) +#endif +#endif + +#ifdef NO_DECLTYPE +#define DECLTYPE(x) +#define DECLTYPE_ASSIGN(dst, src) \ + do \ + { \ + char **_da_dst = (char **)(&(dst)); \ + *_da_dst = (char *)(src); \ + } while (0) +#else +#define DECLTYPE_ASSIGN(dst, src) \ + do \ + { \ + (dst) = DECLTYPE(dst)(src); \ + } while (0) +#endif + +#ifndef uthash_malloc +#define uthash_malloc(sz) malloc(sz) /* malloc fcn */ +#endif +#ifndef uthash_free +#define uthash_free(ptr, sz) free(ptr) /* free fcn */ +#endif +#ifndef uthash_bzero +#define uthash_bzero(a, n) memset(a, '\0', n) +#endif +#ifndef uthash_strlen +#define uthash_strlen(s) strlen(s) +#endif + +#ifndef HASH_FUNCTION +#define HASH_FUNCTION(keyptr, keylen, hashv) HASH_JEN(keyptr, keylen, hashv) +#endif + +#ifndef HASH_KEYCMP +#define HASH_KEYCMP(a, b, n) memcmp(a, b, n) +#endif + +#ifndef uthash_noexpand_fyi +#define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ +#endif +#ifndef uthash_expand_fyi +#define uthash_expand_fyi(tbl) /* can be defined to log expands */ +#endif + +#ifndef HASH_NONFATAL_OOM +#define HASH_NONFATAL_OOM 0 +#endif + +#if HASH_NONFATAL_OOM +/* malloc failures can be recovered from */ + +#ifndef uthash_nonfatal_oom +#define uthash_nonfatal_oom(obj) \ + do \ + { \ + } while (0) /* non-fatal OOM error */ +#endif + +#define HASH_RECORD_OOM(oomed) \ + do \ + { \ + (oomed) = 1; \ + } while (0) +#define IF_HASH_NONFATAL_OOM(x) x + +#else +/* malloc failures result in lost memory, hash tables are unusable */ + +#ifndef uthash_fatal +#define uthash_fatal(msg) exit(-1) /* fatal OOM error */ +#endif + +#define HASH_RECORD_OOM(oomed) uthash_fatal("out of memory") +#define IF_HASH_NONFATAL_OOM(x) + +#endif + +/* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS 32U /* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets */ +#define HASH_BKT_CAPACITY_THRESH 10U /* expand when bucket count reaches */ + +/* calculate the element whose hash handle address is hhp */ +#define ELMT_FROM_HH(tbl, hhp) ((void *)(((char *)(hhp)) - ((tbl)->hho))) +/* calculate the hash handle from element address elp */ +#define HH_FROM_ELMT(tbl, elp) ((UT_hash_handle *)(void *)(((char *)(elp)) + ((tbl)->hho))) + +#define HASH_ROLLBACK_BKT(hh, head, itemptrhh) \ + do \ + { \ + struct UT_hash_handle *_hd_hh_item = (itemptrhh); \ + unsigned _hd_bkt; \ + HASH_TO_BKT(_hd_hh_item->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + (head)->hh.tbl->buckets[_hd_bkt].count++; \ + _hd_hh_item->hh_next = NULL; \ + _hd_hh_item->hh_prev = NULL; \ + } while (0) + +#define HASH_VALUE(keyptr, keylen, hashv) \ + do \ + { \ + HASH_FUNCTION(keyptr, keylen, hashv); \ + } while (0) + +#define HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, hashval, out) \ + do \ + { \ + (out) = NULL; \ + if (head) \ + { \ + unsigned _hf_bkt; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt); \ + if (HASH_BLOOM_TEST((head)->hh.tbl, hashval) != 0) \ + { \ + HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[_hf_bkt], keyptr, keylen, hashval, out); \ + } \ + } \ + } while (0) + +#define HASH_FIND(hh, head, keyptr, keylen, out) \ + do \ + { \ + (out) = NULL; \ + if (head) \ + { \ + unsigned _hf_hashv; \ + HASH_VALUE(keyptr, keylen, _hf_hashv); \ + HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out); \ + } \ + } while (0) + +#ifdef HASH_BLOOM +#define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM) +#define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN / 8UL) + (((HASH_BLOOM_BITLEN % 8UL) != 0UL) ? 1UL : 0UL) +#define HASH_BLOOM_MAKE(tbl, oomed) \ + do \ + { \ + (tbl)->bloom_nbits = HASH_BLOOM; \ + (tbl)->bloom_bv = (uint8_t *)uthash_malloc(HASH_BLOOM_BYTELEN); \ + if (!(tbl)->bloom_bv) \ + { \ + HASH_RECORD_OOM(oomed); \ + } \ + else \ + { \ + uthash_bzero((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ + (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ + } \ + } while (0) + +#define HASH_BLOOM_FREE(tbl) \ + do \ + { \ + uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ + } while (0) + +#define HASH_BLOOM_BITSET(bv, idx) (bv[(idx) / 8U] |= (1U << ((idx) % 8U))) +#define HASH_BLOOM_BITTEST(bv, idx) (bv[(idx) / 8U] & (1U << ((idx) % 8U))) + +#define HASH_BLOOM_ADD(tbl, hashv) \ + HASH_BLOOM_BITSET((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) + +#define HASH_BLOOM_TEST(tbl, hashv) \ + HASH_BLOOM_BITTEST((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) + +#else +#define HASH_BLOOM_MAKE(tbl, oomed) +#define HASH_BLOOM_FREE(tbl) +#define HASH_BLOOM_ADD(tbl, hashv) +#define HASH_BLOOM_TEST(tbl, hashv) (1) +#define HASH_BLOOM_BYTELEN 0U +#endif + +#define HASH_MAKE_TABLE(hh, head, oomed) \ + do \ + { \ + (head)->hh.tbl = (UT_hash_table *)uthash_malloc(sizeof(UT_hash_table)); \ + if (!(head)->hh.tbl) \ + { \ + HASH_RECORD_OOM(oomed); \ + } \ + else \ + { \ + uthash_bzero((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head)->hh.tbl->tail = &((head)->hh); \ + (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ + (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ + (head)->hh.tbl->hho = (char *)(&(head)->hh) - (char *)(head); \ + (head)->hh.tbl->buckets = (UT_hash_bucket *)uthash_malloc( \ + HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ + (head)->hh.tbl->signature = HASH_SIGNATURE; \ + if (!(head)->hh.tbl->buckets) \ + { \ + HASH_RECORD_OOM(oomed); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + } \ + else \ + { \ + uthash_bzero((head)->hh.tbl->buckets, \ + HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ + HASH_BLOOM_MAKE((head)->hh.tbl, oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (oomed) { \ + uthash_free((head)->hh.tbl->buckets, \ + HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + }) \ + } \ + } \ + } while (0) + +#define HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, hashval, add, replaced, cmpfcn) \ + do \ + { \ + (replaced) = NULL; \ + HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ + if (replaced) \ + { \ + HASH_DELETE(hh, head, replaced); \ + } \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \ + } while (0) + +#define HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, hashval, add, replaced) \ + do \ + { \ + (replaced) = NULL; \ + HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ + if (replaced) \ + { \ + HASH_DELETE(hh, head, replaced); \ + } \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \ + } while (0) + +#define HASH_REPLACE(hh, head, fieldname, keylen_in, add, replaced) \ + do \ + { \ + unsigned _hr_hashv; \ + HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ + HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \ + } while (0) + +#define HASH_REPLACE_INORDER(hh, head, fieldname, keylen_in, add, replaced, cmpfcn) \ + do \ + { \ + unsigned _hr_hashv; \ + HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ + HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \ + } while (0) + +#define HASH_APPEND_LIST(hh, head, add) \ + do \ + { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ + (head)->hh.tbl->tail->next = (add); \ + (head)->hh.tbl->tail = &((add)->hh); \ + } while (0) + +#define HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn) \ + do \ + { \ + do \ + { \ + if (cmpfcn(DECLTYPE(head)(_hs_iter), add) > 0) \ + { \ + break; \ + } \ + } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ + } while (0) + +#ifdef NO_DECLTYPE +#undef HASH_AKBI_INNER_LOOP +#define HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn) \ + do \ + { \ + char *_hs_saved_head = (char *)(head); \ + do \ + { \ + DECLTYPE_ASSIGN(head, _hs_iter); \ + if (cmpfcn(head, add) > 0) \ + { \ + DECLTYPE_ASSIGN(head, _hs_saved_head); \ + break; \ + } \ + DECLTYPE_ASSIGN(head, _hs_saved_head); \ + } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ + } while (0) +#endif + +#if HASH_NONFATAL_OOM + +#define HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, oomed) \ + do \ + { \ + if (!(oomed)) \ + { \ + unsigned _ha_bkt; \ + (head)->hh.tbl->num_items++; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ + if (oomed) \ + { \ + HASH_ROLLBACK_BKT(hh, head, &(add)->hh); \ + HASH_DELETE_HH(hh, head, &(add)->hh); \ + (add)->hh.tbl = NULL; \ + uthash_nonfatal_oom(add); \ + } \ + else \ + { \ + HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ + HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ + } \ + } \ + else \ + { \ + (add)->hh.tbl = NULL; \ + uthash_nonfatal_oom(add); \ + } \ + } while (0) + +#else + +#define HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, oomed) \ + do \ + { \ + unsigned _ha_bkt; \ + (head)->hh.tbl->num_items++; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ + HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ + HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ + } while (0) + +#endif + +#define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, hashval, add, cmpfcn) \ + do \ + { \ + IF_HASH_NONFATAL_OOM(int _ha_oomed = 0;) \ + (add)->hh.hashv = (hashval); \ + (add)->hh.key = (char *)(keyptr); \ + (add)->hh.keylen = (unsigned)(keylen_in); \ + if (!(head)) \ + { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh, add, _ha_oomed); \ + IF_HASH_NONFATAL_OOM(if (!_ha_oomed) { ) \ + (head) = (add); \ + IF_HASH_NONFATAL_OOM( }) \ + } \ + else \ + { \ + void *_hs_iter = (head); \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn); \ + if (_hs_iter) \ + { \ + (add)->hh.next = _hs_iter; \ + if (((add)->hh.prev = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev)) \ + { \ + HH_FROM_ELMT((head)->hh.tbl, (add)->hh.prev)->next = (add); \ + } \ + else \ + { \ + (head) = (add); \ + } \ + HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev = (add); \ + } \ + else \ + { \ + HASH_APPEND_LIST(hh, head, add); \ + } \ + } \ + HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ + HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE_INORDER"); \ + } while (0) + +#define HASH_ADD_KEYPTR_INORDER(hh, head, keyptr, keylen_in, add, cmpfcn) \ + do \ + { \ + unsigned _hs_hashv; \ + HASH_VALUE(keyptr, keylen_in, _hs_hashv); \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \ + } while (0) + +#define HASH_ADD_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, hashval, add, cmpfcn) \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn) + +#define HASH_ADD_INORDER(hh, head, fieldname, keylen_in, add, cmpfcn) \ + HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn) + +#define HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, hashval, add) \ + do \ + { \ + IF_HASH_NONFATAL_OOM(int _ha_oomed = 0;) \ + (add)->hh.hashv = (hashval); \ + (add)->hh.key = (const void *)(keyptr); \ + (add)->hh.keylen = (unsigned)(keylen_in); \ + if (!(head)) \ + { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh, add, _ha_oomed); \ + IF_HASH_NONFATAL_OOM(if (!_ha_oomed) { ) \ + (head) = (add); \ + IF_HASH_NONFATAL_OOM( }) \ + } \ + else \ + { \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_APPEND_LIST(hh, head, add); \ + } \ + HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ + HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE"); \ + } while (0) + +#define HASH_ADD_KEYPTR(hh, head, keyptr, keylen_in, add) \ + do \ + { \ + unsigned _ha_hashv; \ + HASH_VALUE(keyptr, keylen_in, _ha_hashv); \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add); \ + } while (0) + +#define HASH_ADD_BYHASHVALUE(hh, head, fieldname, keylen_in, hashval, add) \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add) + +#define HASH_ADD(hh, head, fieldname, keylen_in, add) \ + HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add) + +#define HASH_TO_BKT(hashv, num_bkts, bkt) \ + do \ + { \ + bkt = ((hashv) & ((num_bkts)-1U)); \ + } while (0) + +/* delete "delptr" from the hash table. + * "the usual" patch-up process for the app-order doubly-linked-list. + * The use of _hd_hh_del below deserves special explanation. + * These used to be expressed using (delptr) but that led to a bug + * if someone used the same symbol for the head and deletee, like + * HASH_DELETE(hh,users,users); + * We want that to work, but by changing the head (users) below + * we were forfeiting our ability to further refer to the deletee (users) + * in the patch-up process. Solution: use scratch space to + * copy the deletee pointer, then the latter references are via that + * scratch pointer rather than through the repointed (users) symbol. + */ +#define HASH_DELETE(hh, head, delptr) \ + HASH_DELETE_HH(hh, head, &(delptr)->hh) + +#define HASH_DELETE_HH(hh, head, delptrhh) \ + do \ + { \ + struct UT_hash_handle *_hd_hh_del = (delptrhh); \ + if ((_hd_hh_del->prev == NULL) && (_hd_hh_del->next == NULL)) \ + { \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head) = NULL; \ + } \ + else \ + { \ + unsigned _hd_bkt; \ + if (_hd_hh_del == (head)->hh.tbl->tail) \ + { \ + (head)->hh.tbl->tail = HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev); \ + } \ + if (_hd_hh_del->prev != NULL) \ + { \ + HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev)->next = _hd_hh_del->next; \ + } \ + else \ + { \ + DECLTYPE_ASSIGN(head, _hd_hh_del->next); \ + } \ + if (_hd_hh_del->next != NULL) \ + { \ + HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->next)->prev = _hd_hh_del->prev; \ + } \ + HASH_TO_BKT(_hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + HASH_DEL_IN_BKT((head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ + (head)->hh.tbl->num_items--; \ + } \ + HASH_FSCK(hh, head, "HASH_DELETE_HH"); \ + } while (0) + +/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ +#define HASH_FIND_STR(head, findstr, out) \ + do \ + { \ + unsigned _uthash_hfstr_keylen = (unsigned)uthash_strlen(findstr); \ + HASH_FIND(hh, head, findstr, _uthash_hfstr_keylen, out); \ + } while (0) +#define HASH_ADD_STR(head, strfield, add) \ + do \ + { \ + unsigned _uthash_hastr_keylen = (unsigned)uthash_strlen((add)->strfield); \ + HASH_ADD(hh, head, strfield[0], _uthash_hastr_keylen, add); \ + } while (0) +#define HASH_REPLACE_STR(head, strfield, add, replaced) \ + do \ + { \ + unsigned _uthash_hrstr_keylen = (unsigned)uthash_strlen((add)->strfield); \ + HASH_REPLACE(hh, head, strfield[0], _uthash_hrstr_keylen, add, replaced); \ + } while (0) +#define HASH_FIND_INT(head, findint, out) \ + HASH_FIND(hh, head, findint, sizeof(int), out) +#define HASH_ADD_INT(head, intfield, add) \ + HASH_ADD(hh, head, intfield, sizeof(int), add) +#define HASH_REPLACE_INT(head, intfield, add, replaced) \ + HASH_REPLACE(hh, head, intfield, sizeof(int), add, replaced) +#define HASH_FIND_PTR(head, findptr, out) \ + HASH_FIND(hh, head, findptr, sizeof(void *), out) +#define HASH_ADD_PTR(head, ptrfield, add) \ + HASH_ADD(hh, head, ptrfield, sizeof(void *), add) +#define HASH_REPLACE_PTR(head, ptrfield, add, replaced) \ + HASH_REPLACE(hh, head, ptrfield, sizeof(void *), add, replaced) +#define HASH_DEL(head, delptr) \ + HASH_DELETE(hh, head, delptr) + +/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined. + * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. + */ +#ifdef HASH_DEBUG +#include /* fprintf, stderr */ +#define HASH_OOPS(...) \ + do \ + { \ + fprintf(stderr, __VA_ARGS__); \ + exit(-1); \ + } while (0) +#define HASH_FSCK(hh, head, where) \ + do \ + { \ + struct UT_hash_handle *_thh; \ + if (head) \ + { \ + unsigned _bkt_i; \ + unsigned _count = 0; \ + char *_prev; \ + for (_bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; ++_bkt_i) \ + { \ + unsigned _bkt_count = 0; \ + _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ + _prev = NULL; \ + while (_thh) \ + { \ + if (_prev != (char *)(_thh->hh_prev)) \ + { \ + HASH_OOPS("%s: invalid hh_prev %p, actual %p\n", \ + (where), (void *)_thh->hh_prev, (void *)_prev); \ + } \ + _bkt_count++; \ + _prev = (char *)(_thh); \ + _thh = _thh->hh_next; \ + } \ + _count += _bkt_count; \ + if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) \ + { \ + HASH_OOPS("%s: invalid bucket count %u, actual %u\n", \ + (where), (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ + } \ + } \ + if (_count != (head)->hh.tbl->num_items) \ + { \ + HASH_OOPS("%s: invalid hh item count %u, actual %u\n", \ + (where), (head)->hh.tbl->num_items, _count); \ + } \ + _count = 0; \ + _prev = NULL; \ + _thh = &(head)->hh; \ + while (_thh) \ + { \ + _count++; \ + if (_prev != (char *)_thh->prev) \ + { \ + HASH_OOPS("%s: invalid prev %p, actual %p\n", \ + (where), (void *)_thh->prev, (void *)_prev); \ + } \ + _prev = (char *)ELMT_FROM_HH((head)->hh.tbl, _thh); \ + _thh = (_thh->next ? HH_FROM_ELMT((head)->hh.tbl, _thh->next) : NULL); \ + } \ + if (_count != (head)->hh.tbl->num_items) \ + { \ + HASH_OOPS("%s: invalid app item count %u, actual %u\n", \ + (where), (head)->hh.tbl->num_items, _count); \ + } \ + } \ + } while (0) +#else +#define HASH_FSCK(hh, head, where) +#endif + +/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to + * the descriptor to which this macro is defined for tuning the hash function. + * The app can #include to get the prototype for write(2). */ +#ifdef HASH_EMIT_KEYS +#define HASH_EMIT_KEY(hh, head, keyptr, fieldlen) \ + do \ + { \ + unsigned _klen = fieldlen; \ + write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ + write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen); \ + } while (0) +#else +#define HASH_EMIT_KEY(hh, head, keyptr, fieldlen) +#endif + +/* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */ +#define HASH_BER(key, keylen, hashv) \ + do \ + { \ + unsigned _hb_keylen = (unsigned)keylen; \ + const unsigned char *_hb_key = (const unsigned char *)(key); \ + (hashv) = 0; \ + while (_hb_keylen-- != 0U) \ + { \ + (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++; \ + } \ + } while (0) + +/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at + * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx */ +#define HASH_SAX(key, keylen, hashv) \ + do \ + { \ + unsigned _sx_i; \ + const unsigned char *_hs_key = (const unsigned char *)(key); \ + hashv = 0; \ + for (_sx_i = 0; _sx_i < keylen; _sx_i++) \ + { \ + hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ + } \ + } while (0) +/* FNV-1a variation */ +#define HASH_FNV(key, keylen, hashv) \ + do \ + { \ + unsigned _fn_i; \ + const unsigned char *_hf_key = (const unsigned char *)(key); \ + (hashv) = 2166136261U; \ + for (_fn_i = 0; _fn_i < keylen; _fn_i++) \ + { \ + hashv = hashv ^ _hf_key[_fn_i]; \ + hashv = hashv * 16777619U; \ + } \ + } while (0) + +#define HASH_OAT(key, keylen, hashv) \ + do \ + { \ + unsigned _ho_i; \ + const unsigned char *_ho_key = (const unsigned char *)(key); \ + hashv = 0; \ + for (_ho_i = 0; _ho_i < keylen; _ho_i++) \ + { \ + hashv += _ho_key[_ho_i]; \ + hashv += (hashv << 10); \ + hashv ^= (hashv >> 6); \ + } \ + hashv += (hashv << 3); \ + hashv ^= (hashv >> 11); \ + hashv += (hashv << 15); \ + } while (0) + +#define HASH_JEN_MIX(a, b, c) \ + do \ + { \ + a -= b; \ + a -= c; \ + a ^= (c >> 13); \ + b -= c; \ + b -= a; \ + b ^= (a << 8); \ + c -= a; \ + c -= b; \ + c ^= (b >> 13); \ + a -= b; \ + a -= c; \ + a ^= (c >> 12); \ + b -= c; \ + b -= a; \ + b ^= (a << 16); \ + c -= a; \ + c -= b; \ + c ^= (b >> 5); \ + a -= b; \ + a -= c; \ + a ^= (c >> 3); \ + b -= c; \ + b -= a; \ + b ^= (a << 10); \ + c -= a; \ + c -= b; \ + c ^= (b >> 15); \ + } while (0) + +#define HASH_JEN(key, keylen, hashv) \ + do \ + { \ + unsigned _hj_i, _hj_j, _hj_k; \ + unsigned const char *_hj_key = (unsigned const char *)(key); \ + hashv = 0xfeedbeefu; \ + _hj_i = _hj_j = 0x9e3779b9u; \ + _hj_k = (unsigned)(keylen); \ + while (_hj_k >= 12U) \ + { \ + _hj_i += (_hj_key[0] + ((unsigned)_hj_key[1] << 8) + ((unsigned)_hj_key[2] << 16) + ((unsigned)_hj_key[3] << 24)); \ + _hj_j += (_hj_key[4] + ((unsigned)_hj_key[5] << 8) + ((unsigned)_hj_key[6] << 16) + ((unsigned)_hj_key[7] << 24)); \ + hashv += (_hj_key[8] + ((unsigned)_hj_key[9] << 8) + ((unsigned)_hj_key[10] << 16) + ((unsigned)_hj_key[11] << 24)); \ + \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ + \ + _hj_key += 12; \ + _hj_k -= 12U; \ + } \ + hashv += (unsigned)(keylen); \ + switch (_hj_k) \ + { \ + case 11: \ + hashv += ((unsigned)_hj_key[10] << 24); /* FALLTHROUGH */ \ + case 10: \ + hashv += ((unsigned)_hj_key[9] << 16); /* FALLTHROUGH */ \ + case 9: \ + hashv += ((unsigned)_hj_key[8] << 8); /* FALLTHROUGH */ \ + case 8: \ + _hj_j += ((unsigned)_hj_key[7] << 24); /* FALLTHROUGH */ \ + case 7: \ + _hj_j += ((unsigned)_hj_key[6] << 16); /* FALLTHROUGH */ \ + case 6: \ + _hj_j += ((unsigned)_hj_key[5] << 8); /* FALLTHROUGH */ \ + case 5: \ + _hj_j += _hj_key[4]; /* FALLTHROUGH */ \ + case 4: \ + _hj_i += ((unsigned)_hj_key[3] << 24); /* FALLTHROUGH */ \ + case 3: \ + _hj_i += ((unsigned)_hj_key[2] << 16); /* FALLTHROUGH */ \ + case 2: \ + _hj_i += ((unsigned)_hj_key[1] << 8); /* FALLTHROUGH */ \ + case 1: \ + _hj_i += _hj_key[0]; /* FALLTHROUGH */ \ + default:; \ + } \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ + } while (0) + +/* The Paul Hsieh hash function */ +#undef get16bits +#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) || defined(_MSC_VER) || defined(__BORLANDC__) || defined(__TURBOC__) +#define get16bits(d) (*((const uint16_t *)(d))) +#endif + +#if !defined(get16bits) +#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) + (uint32_t)(((const uint8_t *)(d))[0])) +#endif +#define HASH_SFH(key, keylen, hashv) \ + do \ + { \ + unsigned const char *_sfh_key = (unsigned const char *)(key); \ + uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen; \ + \ + unsigned _sfh_rem = _sfh_len & 3U; \ + _sfh_len >>= 2; \ + hashv = 0xcafebabeu; \ + \ + /* Main loop */ \ + for (; _sfh_len > 0U; _sfh_len--) \ + { \ + hashv += get16bits(_sfh_key); \ + _sfh_tmp = ((uint32_t)(get16bits(_sfh_key + 2)) << 11) ^ hashv; \ + hashv = (hashv << 16) ^ _sfh_tmp; \ + _sfh_key += 2U * sizeof(uint16_t); \ + hashv += hashv >> 11; \ + } \ + \ + /* Handle end cases */ \ + switch (_sfh_rem) \ + { \ + case 3: \ + hashv += get16bits(_sfh_key); \ + hashv ^= hashv << 16; \ + hashv ^= (uint32_t)(_sfh_key[sizeof(uint16_t)]) << 18; \ + hashv += hashv >> 11; \ + break; \ + case 2: \ + hashv += get16bits(_sfh_key); \ + hashv ^= hashv << 11; \ + hashv += hashv >> 17; \ + break; \ + case 1: \ + hashv += *_sfh_key; \ + hashv ^= hashv << 10; \ + hashv += hashv >> 1; \ + break; \ + default:; \ + } \ + \ + /* Force "avalanching" of final 127 bits */ \ + hashv ^= hashv << 3; \ + hashv += hashv >> 5; \ + hashv ^= hashv << 4; \ + hashv += hashv >> 17; \ + hashv ^= hashv << 25; \ + hashv += hashv >> 6; \ + } while (0) + +/* iterate over items in a known bucket to find desired item */ +#define HASH_FIND_IN_BKT(tbl, hh, head, keyptr, keylen_in, hashval, out) \ + do \ + { \ + if ((head).hh_head != NULL) \ + { \ + DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head)); \ + } \ + else \ + { \ + (out) = NULL; \ + } \ + while ((out) != NULL) \ + { \ + if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) \ + { \ + if (HASH_KEYCMP((out)->hh.key, keyptr, keylen_in) == 0) \ + { \ + break; \ + } \ + } \ + if ((out)->hh.hh_next != NULL) \ + { \ + DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next)); \ + } \ + else \ + { \ + (out) = NULL; \ + } \ + } \ + } while (0) + +/* add an item to a bucket */ +#define HASH_ADD_TO_BKT(head, hh, addhh, oomed) \ + do \ + { \ + UT_hash_bucket *_ha_head = &(head); \ + _ha_head->count++; \ + (addhh)->hh_next = _ha_head->hh_head; \ + (addhh)->hh_prev = NULL; \ + if (_ha_head->hh_head != NULL) \ + { \ + _ha_head->hh_head->hh_prev = (addhh); \ + } \ + _ha_head->hh_head = (addhh); \ + if ((_ha_head->count >= ((_ha_head->expand_mult + 1U) * HASH_BKT_CAPACITY_THRESH)) && !(addhh)->tbl->noexpand) \ + { \ + HASH_EXPAND_BUCKETS(addhh, (addhh)->tbl, oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (oomed) { \ + HASH_DEL_IN_BKT(head, addhh); \ + }) \ + } \ + } while (0) + +/* remove an item from a given bucket */ +#define HASH_DEL_IN_BKT(head, delhh) \ + do \ + { \ + UT_hash_bucket *_hd_head = &(head); \ + _hd_head->count--; \ + if (_hd_head->hh_head == (delhh)) \ + { \ + _hd_head->hh_head = (delhh)->hh_next; \ + } \ + if ((delhh)->hh_prev) \ + { \ + (delhh)->hh_prev->hh_next = (delhh)->hh_next; \ + } \ + if ((delhh)->hh_next) \ + { \ + (delhh)->hh_next->hh_prev = (delhh)->hh_prev; \ + } \ + } while (0) + +/* Bucket expansion has the effect of doubling the number of buckets + * and redistributing the items into the new buckets. Ideally the + * items will distribute more or less evenly into the new buckets + * (the extent to which this is true is a measure of the quality of + * the hash function as it applies to the key domain). + * + * With the items distributed into more buckets, the chain length + * (item count) in each bucket is reduced. Thus by expanding buckets + * the hash keeps a bound on the chain length. This bounded chain + * length is the essence of how a hash provides constant time lookup. + * + * The calculation of tbl->ideal_chain_maxlen below deserves some + * explanation. First, keep in mind that we're calculating the ideal + * maximum chain length based on the *new* (doubled) bucket count. + * In fractions this is just n/b (n=number of items,b=new num buckets). + * Since the ideal chain length is an integer, we want to calculate + * ceil(n/b). We don't depend on floating point arithmetic in this + * hash, so to calculate ceil(n/b) with integers we could write + * + * ceil(n/b) = (n/b) + ((n%b)?1:0) + * + * and in fact a previous version of this hash did just that. + * But now we have improved things a bit by recognizing that b is + * always a power of two. We keep its base 2 log handy (call it lb), + * so now we can write this with a bit shift and logical AND: + * + * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) + * + */ +#define HASH_EXPAND_BUCKETS(hh, tbl, oomed) \ + do \ + { \ + unsigned _he_bkt; \ + unsigned _he_bkt_i; \ + struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ + UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ + _he_new_buckets = (UT_hash_bucket *)uthash_malloc( \ + sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ + if (!_he_new_buckets) \ + { \ + HASH_RECORD_OOM(oomed); \ + } \ + else \ + { \ + uthash_bzero(_he_new_buckets, \ + sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ + (tbl)->ideal_chain_maxlen = \ + ((tbl)->num_items >> ((tbl)->log2_num_buckets + 1U)) + \ + ((((tbl)->num_items & (((tbl)->num_buckets * 2U) - 1U)) != 0U) ? 1U : 0U); \ + (tbl)->nonideal_items = 0; \ + for (_he_bkt_i = 0; _he_bkt_i < (tbl)->num_buckets; _he_bkt_i++) \ + { \ + _he_thh = (tbl)->buckets[_he_bkt_i].hh_head; \ + while (_he_thh != NULL) \ + { \ + _he_hh_nxt = _he_thh->hh_next; \ + HASH_TO_BKT(_he_thh->hashv, (tbl)->num_buckets * 2U, _he_bkt); \ + _he_newbkt = &(_he_new_buckets[_he_bkt]); \ + if (++(_he_newbkt->count) > (tbl)->ideal_chain_maxlen) \ + { \ + (tbl)->nonideal_items++; \ + if (_he_newbkt->count > _he_newbkt->expand_mult * (tbl)->ideal_chain_maxlen) \ + { \ + _he_newbkt->expand_mult++; \ + } \ + } \ + _he_thh->hh_prev = NULL; \ + _he_thh->hh_next = _he_newbkt->hh_head; \ + if (_he_newbkt->hh_head != NULL) \ + { \ + _he_newbkt->hh_head->hh_prev = _he_thh; \ + } \ + _he_newbkt->hh_head = _he_thh; \ + _he_thh = _he_hh_nxt; \ + } \ + } \ + uthash_free((tbl)->buckets, (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \ + (tbl)->num_buckets *= 2U; \ + (tbl)->log2_num_buckets++; \ + (tbl)->buckets = _he_new_buckets; \ + (tbl)->ineff_expands = ((tbl)->nonideal_items > ((tbl)->num_items >> 1)) ? ((tbl)->ineff_expands + 1U) : 0U; \ + if ((tbl)->ineff_expands > 1U) \ + { \ + (tbl)->noexpand = 1; \ + uthash_noexpand_fyi(tbl); \ + } \ + uthash_expand_fyi(tbl); \ + } \ + } while (0) + +/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ +/* Note that HASH_SORT assumes the hash handle name to be hh. + * HASH_SRT was added to allow the hash handle name to be passed in. */ +#define HASH_SORT(head, cmpfcn) HASH_SRT(hh, head, cmpfcn) +#define HASH_SRT(hh, head, cmpfcn) \ + do \ + { \ + unsigned _hs_i; \ + unsigned _hs_looping, _hs_nmerges, _hs_insize, _hs_psize, _hs_qsize; \ + struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ + if (head != NULL) \ + { \ + _hs_insize = 1; \ + _hs_looping = 1; \ + _hs_list = &((head)->hh); \ + while (_hs_looping != 0U) \ + { \ + _hs_p = _hs_list; \ + _hs_list = NULL; \ + _hs_tail = NULL; \ + _hs_nmerges = 0; \ + while (_hs_p != NULL) \ + { \ + _hs_nmerges++; \ + _hs_q = _hs_p; \ + _hs_psize = 0; \ + for (_hs_i = 0; _hs_i < _hs_insize; ++_hs_i) \ + { \ + _hs_psize++; \ + _hs_q = ((_hs_q->next != NULL) ? HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + if (_hs_q == NULL) \ + { \ + break; \ + } \ + } \ + _hs_qsize = _hs_insize; \ + while ((_hs_psize != 0U) || ((_hs_qsize != 0U) && (_hs_q != NULL))) \ + { \ + if (_hs_psize == 0U) \ + { \ + _hs_e = _hs_q; \ + _hs_q = ((_hs_q->next != NULL) ? HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + _hs_qsize--; \ + } \ + else if ((_hs_qsize == 0U) || (_hs_q == NULL)) \ + { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL) \ + { \ + _hs_p = ((_hs_p->next != NULL) ? HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ + } \ + _hs_psize--; \ + } \ + else if ((cmpfcn( \ + DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_p)), \ + DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_q)))) <= 0) \ + { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL) \ + { \ + _hs_p = ((_hs_p->next != NULL) ? HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ + } \ + _hs_psize--; \ + } \ + else \ + { \ + _hs_e = _hs_q; \ + _hs_q = ((_hs_q->next != NULL) ? HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + _hs_qsize--; \ + } \ + if (_hs_tail != NULL) \ + { \ + _hs_tail->next = ((_hs_e != NULL) ? ELMT_FROM_HH((head)->hh.tbl, _hs_e) : NULL); \ + } \ + else \ + { \ + _hs_list = _hs_e; \ + } \ + if (_hs_e != NULL) \ + { \ + _hs_e->prev = ((_hs_tail != NULL) ? ELMT_FROM_HH((head)->hh.tbl, _hs_tail) : NULL); \ + } \ + _hs_tail = _hs_e; \ + } \ + _hs_p = _hs_q; \ + } \ + if (_hs_tail != NULL) \ + { \ + _hs_tail->next = NULL; \ + } \ + if (_hs_nmerges <= 1U) \ + { \ + _hs_looping = 0; \ + (head)->hh.tbl->tail = _hs_tail; \ + DECLTYPE_ASSIGN(head, ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ + } \ + _hs_insize *= 2U; \ + } \ + HASH_FSCK(hh, head, "HASH_SRT"); \ + } \ + } while (0) + +/* This function selects items from one hash into another hash. + * The end result is that the selected items have dual presence + * in both hashes. There is no copy of the items made; rather + * they are added into the new hash through a secondary hash + * hash handle that must be present in the structure. */ +#define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ + do \ + { \ + unsigned _src_bkt, _dst_bkt; \ + void *_last_elt = NULL, *_elt; \ + UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh = NULL; \ + ptrdiff_t _dst_hho = ((char *)(&(dst)->hh_dst) - (char *)(dst)); \ + if ((src) != NULL) \ + { \ + for (_src_bkt = 0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) \ + { \ + for (_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ + _src_hh != NULL; \ + _src_hh = _src_hh->hh_next) \ + { \ + _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ + if (cond(_elt)) \ + { \ + IF_HASH_NONFATAL_OOM(int _hs_oomed = 0;) \ + _dst_hh = (UT_hash_handle *)(void *)(((char *)_elt) + _dst_hho); \ + _dst_hh->key = _src_hh->key; \ + _dst_hh->keylen = _src_hh->keylen; \ + _dst_hh->hashv = _src_hh->hashv; \ + _dst_hh->prev = _last_elt; \ + _dst_hh->next = NULL; \ + if (_last_elt_hh != NULL) \ + { \ + _last_elt_hh->next = _elt; \ + } \ + if ((dst) == NULL) \ + { \ + DECLTYPE_ASSIGN(dst, _elt); \ + HASH_MAKE_TABLE(hh_dst, dst, _hs_oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (_hs_oomed) { \ + uthash_nonfatal_oom(_elt); \ + (dst) = NULL; \ + continue; \ + }) \ + } \ + else \ + { \ + _dst_hh->tbl = (dst)->hh_dst.tbl; \ + } \ + HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ + HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt], hh_dst, _dst_hh, _hs_oomed); \ + (dst)->hh_dst.tbl->num_items++; \ + IF_HASH_NONFATAL_OOM( \ + if (_hs_oomed) { \ + HASH_ROLLBACK_BKT(hh_dst, dst, _dst_hh); \ + HASH_DELETE_HH(hh_dst, dst, _dst_hh); \ + _dst_hh->tbl = NULL; \ + uthash_nonfatal_oom(_elt); \ + continue; \ + }) \ + HASH_BLOOM_ADD(_dst_hh->tbl, _dst_hh->hashv); \ + _last_elt = _elt; \ + _last_elt_hh = _dst_hh; \ + } \ + } \ + } \ + } \ + HASH_FSCK(hh_dst, dst, "HASH_SELECT"); \ + } while (0) + +#define HASH_CLEAR(hh, head) \ + do \ + { \ + if ((head) != NULL) \ + { \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head) = NULL; \ + } \ + } while (0) + +#define HASH_OVERHEAD(hh, head) \ + (((head) != NULL) ? ( \ + (size_t)(((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \ + ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \ + sizeof(UT_hash_table) + \ + (HASH_BLOOM_BYTELEN))) \ + : 0U) + +#ifdef NO_DECLTYPE +#define HASH_ITER(hh, head, el, tmp) \ + for (((el) = (head)), ((*(char **)(&(tmp))) = (char *)((head != NULL) ? (head)->hh.next : NULL)); \ + (el) != NULL; ((el) = (tmp)), ((*(char **)(&(tmp))) = (char *)((tmp != NULL) ? (tmp)->hh.next : NULL))) +#else +#define HASH_ITER(hh, head, el, tmp) \ + for (((el) = (head)), ((tmp) = DECLTYPE(el)((head != NULL) ? (head)->hh.next : NULL)); \ + (el) != NULL; ((el) = (tmp)), ((tmp) = DECLTYPE(el)((tmp != NULL) ? (tmp)->hh.next : NULL))) +#endif + +/* obtain a count of items in the hash */ +#define HASH_COUNT(head) HASH_CNT(hh, head) +#define HASH_CNT(hh, head) ((head != NULL) ? ((head)->hh.tbl->num_items) : 0U) + +typedef struct UT_hash_bucket +{ + struct UT_hash_handle *hh_head; + unsigned count; + + /* expand_mult is normally set to 0. In this situation, the max chain length + * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If + * the bucket's chain exceeds this length, bucket expansion is triggered). + * However, setting expand_mult to a non-zero value delays bucket expansion + * (that would be triggered by additions to this particular bucket) + * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. + * (The multiplier is simply expand_mult+1). The whole idea of this + * multiplier is to reduce bucket expansions, since they are expensive, in + * situations where we know that a particular bucket tends to be overused. + * It is better to let its chain length grow to a longer yet-still-bounded + * value, than to do an O(n) bucket expansion too often. + */ + unsigned expand_mult; + +} UT_hash_bucket; + +/* random signature used only to find hash tables in external analysis */ +#define HASH_SIGNATURE 0xa0111fe1u +#define HASH_BLOOM_SIGNATURE 0xb12220f2u + +typedef struct UT_hash_table +{ + UT_hash_bucket *buckets; + unsigned num_buckets, log2_num_buckets; + unsigned num_items; + struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ + ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ + + /* in an ideal situation (all buckets used equally), no bucket would have + * more than ceil(#items/#buckets) items. that's the ideal chain length. */ + unsigned ideal_chain_maxlen; + + /* nonideal_items is the number of items in the hash whose chain position + * exceeds the ideal chain maxlen. these items pay the penalty for an uneven + * hash distribution; reaching them in a chain traversal takes >ideal steps */ + unsigned nonideal_items; + + /* ineffective expands occur when a bucket doubling was performed, but + * afterward, more than half the items in the hash had nonideal chain + * positions. If this happens on two consecutive expansions we inhibit any + * further expansion, as it's not helping; this happens when the hash + * function isn't a good fit for the key domain. When expansion is inhibited + * the hash will still work, albeit no longer in constant time. */ + unsigned ineff_expands, noexpand; + + uint32_t signature; /* used only to find hash tables in external analysis */ +#ifdef HASH_BLOOM + uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ + uint8_t *bloom_bv; + uint8_t bloom_nbits; +#endif + +} UT_hash_table; + +typedef struct UT_hash_handle +{ + struct UT_hash_table *tbl; + void *prev; /* prev element in app order */ + void *next; /* next element in app order */ + struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ + struct UT_hash_handle *hh_next; /* next hh in bucket order */ + const void *key; /* ptr to enclosing struct's key */ + unsigned keylen; /* enclosing struct's key len */ + unsigned hashv; /* result of hash-fcn(key) */ +} UT_hash_handle; + +#endif /* UTHASH_H */ diff --git a/common/src/tfe_acceptor_kni.cpp b/common/src/tfe_acceptor_kni.cpp new file mode 100644 index 0000000..13e8846 --- /dev/null +++ b/common/src/tfe_acceptor_kni.cpp @@ -0,0 +1,115 @@ +#include +#include + +#include "tfe_cmsg.h" +#include "tfe_tap_rss.h" +#include "tfe_acceptor_kni.h" +#include "tfe_metrics.h" + +/****************************************************************************** + * session_ctx + ******************************************************************************/ + +struct session_ctx *session_ctx_new() +{ + struct session_ctx *ctx = (struct session_ctx *)calloc(1, sizeof(struct session_ctx)); + assert(ctx != NULL); + return ctx; +} + +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); + } + + free(ctx); + ctx = 0; + } +} + + +/****************************************************************************** + * 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); + MESA_load_profile_int_def(profile, "system", "service_chaining_sids", (int *)&(ctx->sce_sids), 1002); + MESA_load_profile_int_def(profile, "system", "nr_worker_threads", (int *)&(ctx->nr_worker_threads), 8); + MESA_load_profile_uint_range(profile, "system", "cpu_affinity_mask", TFE_THREAD_MAX, (unsigned int *)ctx->cpu_affinity_mask); + ctx->nr_worker_threads = MIN(ctx->nr_worker_threads, TFE_THREAD_MAX); + + CPU_ZERO(&ctx->coremask); + for (int i = 0; i < ctx->nr_worker_threads; i++) + { + int cpu_id = ctx->cpu_affinity_mask[i]; + CPU_SET(cpu_id, &ctx->coremask); + } + + ctx->io = packet_io_create(profile, ctx->nr_worker_threads, &ctx->coremask); + if (ctx->io == NULL) + { + goto error_out; + } + + ctx->config = tfe_tap_config_create(profile, ctx->nr_worker_threads); + if (ctx->config == NULL) + { + goto error_out; + } + + ctx->metrics = global_metrics_create(); + if (ctx->metrics == NULL) + { + 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: + acceptor_ctx_destory(ctx); + return NULL; +} + +void acceptor_ctx_destory(struct acceptor_ctx * ctx) +{ + if (ctx) + { + packet_io_destory(ctx->io); + tfe_tap_destory(ctx->config); + + free(ctx); + ctx = NULL; + } + return; +} diff --git a/common/src/tfe_addr_tuple4.cpp b/common/src/tfe_addr_tuple4.cpp new file mode 100644 index 0000000..24653a6 --- /dev/null +++ b/common/src/tfe_addr_tuple4.cpp @@ -0,0 +1,57 @@ +#include +#include +#include + +#include "tfe_addr_tuple4.h" + +char *addr_tuple4_to_str(const struct addr_tuple4 *addr) +{ + char *str_ret = NULL; + + if (addr->addr_type == ADDR_TUPLE4_TYPE_V4) + { + char src_addr[INET_ADDRSTRLEN] = {0}; + char dst_addr[INET_ADDRSTRLEN] = {0}; + uint16_t src_port = ntohs((uint16_t)addr->src_port); + uint16_t dst_port = ntohs((uint16_t)addr->dst_port); + inet_ntop(AF_INET, &addr->addr_v4.src_addr, src_addr, sizeof(src_addr)); + inet_ntop(AF_INET, &addr->addr_v4.dst_addr, dst_addr, sizeof(dst_addr)); + asprintf(&str_ret, "%s %u %s %u", src_addr, src_port, dst_addr, dst_port); + } + + if (addr->addr_type == ADDR_TUPLE4_TYPE_V6) + { + char src_addr[INET6_ADDRSTRLEN] = {0}; + char dst_addr[INET6_ADDRSTRLEN] = {0}; + uint16_t src_port = ntohs((uint16_t)addr->src_port); + uint16_t dst_port = ntohs((uint16_t)addr->dst_port); + inet_ntop(AF_INET6, &addr->addr_v6.src_addr, src_addr, sizeof(src_addr)); + inet_ntop(AF_INET6, &addr->addr_v6.dst_addr, dst_addr, sizeof(dst_addr)); + asprintf(&str_ret, "%s %u %s %u", src_addr, src_port, dst_addr, dst_port); + } + + return str_ret; +} + +void addr_tuple4_reverse(const struct addr_tuple4 *orin, struct addr_tuple4 *out) +{ + memset(out, 0, sizeof(struct addr_tuple4)); + + if (orin->addr_type == ADDR_TUPLE4_TYPE_V4) + { + out->addr_type = ADDR_TUPLE4_TYPE_V4; + out->addr_v4.src_addr = orin->addr_v4.dst_addr; + out->addr_v4.dst_addr = orin->addr_v4.src_addr; + out->src_port = orin->dst_port; + out->dst_port = orin->src_port; + } + + if (orin->addr_type == ADDR_TUPLE4_TYPE_V6) + { + out->addr_type = ADDR_TUPLE4_TYPE_V6; + out->addr_v6.src_addr = orin->addr_v6.dst_addr; + out->addr_v6.dst_addr = orin->addr_v6.src_addr; + out->src_port = orin->dst_port; + out->dst_port = orin->src_port; + } +} \ No newline at end of file diff --git a/common/src/tfe_ctrl_packet.cpp b/common/src/tfe_ctrl_packet.cpp new file mode 100644 index 0000000..0b6b3dc --- /dev/null +++ b/common/src/tfe_ctrl_packet.cpp @@ -0,0 +1,142 @@ +#include +#include +#include + +#include "tfe_mpack.h" +#include "tfe_cmsg.h" +#include "tfe_utils.h" +#include "tfe_ctrl_packet.h" + +const char *session_state_to_string(enum session_state state) +{ + switch (state) + { + case SESSION_STATE_OPENING: + return "opening"; + case SESSION_STATE_CLOSING: + return "closing"; + case SESSION_STATE_ACTIVE: + return "active"; + case SESSION_STATE_RESETALL: + return "resetall"; + default: + return "unknown"; + } +} + +void ctrl_packet_parser_init(struct ctrl_pkt_parser *handler) +{ + memset(handler, 0, sizeof(struct ctrl_pkt_parser)); + handler->cmsg = tfe_cmsg_init(); +} + +// return 0 : success +// 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); +} + +void ctrl_packet_parser_dump(struct ctrl_pkt_parser *handler) +{ + uint16_t cmsg_len; + if (handler) + { + TFE_LOG_INFO(g_default_logger, "%s: tsync : %s", LOG_TAG_POLICY, handler->tsync); + TFE_LOG_INFO(g_default_logger, "%s: session_id : %lu", LOG_TAG_POLICY, handler->session_id); + TFE_LOG_INFO(g_default_logger, "%s: state : %s", LOG_TAG_POLICY, session_state_to_string(handler->state)); + TFE_LOG_INFO(g_default_logger, "%s: method : %s", LOG_TAG_POLICY, handler->method); + TFE_LOG_INFO(g_default_logger, "%s: tfe policy_id_num : %d", LOG_TAG_POLICY, handler->tfe_policy_id_num); + + for (int i = 0; i < handler->tfe_policy_id_num; i++) + { + TFE_LOG_INFO(g_default_logger, "%s: %d tfe policy_ids[%03lu]", LOG_TAG_POLICY, i, handler->tfe_policy_ids[i]); + } + TFE_LOG_INFO(g_default_logger, "%s: sce policy_id_num : %d", LOG_TAG_POLICY, handler->sce_policy_id_num); + + for (int i = 0; i < handler->tfe_policy_id_num; i++) + { + 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); + + } +} + +const char * tfe_cmsg_tlv_type_to_string[TFE_CMSG_TLV_NR_MAX]; +void tfe_cmsg_enum_to_string() +{ + memset(tfe_cmsg_tlv_type_to_string, 0 ,sizeof(tfe_cmsg_tlv_type_to_string)); + tfe_cmsg_tlv_type_to_string[TFE_CMSG_TCP_RESTORE_SEQ] = "TFE_CMSG_TCP_RESTORE_SEQ"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_TCP_RESTORE_ACK] = "TFE_CMSG_TCP_RESTORE_ACK"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_TCP_RESTORE_MSS_CLIENT] = "TFE_CMSG_TCP_RESTORE_MSS_CLIENT"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_TCP_RESTORE_MSS_SERVER] = "TFE_CMSG_TCP_RESTORE_MSS_SERVER"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_TCP_RESTORE_WSACLE_CLIENT] = "TFE_CMSG_TCP_RESTORE_WSACLE_CLIENT"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_TCP_RESTORE_WSACLE_SERVER] = "TFE_CMSG_TCP_RESTORE_WSACLE_SERVER"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_TCP_RESTORE_SACK_CLIENT] = "TFE_CMSG_TCP_RESTORE_SACK_CLIENT"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_TCP_RESTORE_SACK_SERVER] = "TFE_CMSG_TCP_RESTORE_SACK_SERVER"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_TCP_RESTORE_TS_CLIENT] = "TFE_CMSG_TCP_RESTORE_TS_CLIENT"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_TCP_RESTORE_TS_SERVER] = "TFE_CMSG_TCP_RESTORE_TS_SERVER"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_TCP_RESTORE_PROTOCOL] = "TFE_CMSG_TCP_RESTORE_PROTOCOL"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_TCP_RESTORE_WINDOW_CLIENT] = "TFE_CMSG_TCP_RESTORE_WINDOW_CLIENT"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_TCP_RESTORE_WINDOW_SERVER] = "TFE_CMSG_TCP_RESTORE_WINDOW_SERVER"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_TCP_RESTORE_INFO_PACKET_CUR_DIR] = "TFE_CMSG_TCP_RESTORE_INFO_PACKET_CUR_DIR"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_TCP_RESTORE_TS_CLIENT_VAL] = "TFE_CMSG_TCP_RESTORE_TS_CLIENT_VAL"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_TCP_RESTORE_TS_SERVER_VAL] = "TFE_CMSG_TCP_RESTORE_TS_SERVER_VAL"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_POLICY_ID] = "TFE_CMSG_POLICY_ID"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_STREAM_TRACE_ID] = "TFE_CMSG_STREAM_TRACE_ID"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_SSL_INTERCEPT_STATE] = "TFE_CMSG_SSL_INTERCEPT_STATE"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_SSL_SERVER_SIDE_LATENCY] = "TFE_CMSG_SSL_SERVER_SIDE_LATENCY"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_SSL_CLIENT_SIDE_LATENCY] = "TFE_CMSG_SSL_CLIENT_SIDE_LATENCY"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_SSL_SERVER_SIDE_VERSION] = "TFE_CMSG_SSL_SERVER_SIDE_VERSION"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_SSL_CLIENT_SIDE_VERSION] = "TFE_CMSG_SSL_CLIENT_SIDE_VERSION"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_SSL_PINNING_STATE] = "TFE_CMSG_SSL_PINNING_STATE"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_SSL_CERT_VERIFY] = "TFE_CMSG_SSL_CERT_VERIFY"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_SSL_ERROR] = "TFE_CMSG_SSL_ERROR"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_SRC_MAC] = "TFE_CMSG_SRC_MAC"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_DST_MAC] = "TFE_CMSG_DST_MAC"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_DOWNSTREAM_TCP_NODELAY] = "TFE_CMSG_DOWNSTREAM_TCP_NODELAY"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_DOWNSTREAM_TCP_TTL] = "TFE_CMSG_DOWNSTREAM_TCP_TTL"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_DOWNSTREAM_TCP_KEEPALIVE] = "TFE_CMSG_DOWNSTREAM_TCP_KEEPALIVE"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_DOWNSTREAM_TCP_KEEPCNT] = "TFE_CMSG_DOWNSTREAM_TCP_KEEPCNT"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_DOWNSTREAM_TCP_KEEPIDLE] = "TFE_CMSG_DOWNSTREAM_TCP_KEEPIDLE"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_DOWNSTREAM_TCP_KEEPINTVL] = "TFE_CMSG_DOWNSTREAM_TCP_KEEPINTVL"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_DOWNSTREAM_TCP_USER_TIMEOUT] = "TFE_CMSG_DOWNSTREAM_TCP_USER_TIMEOUT"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_UPSTREAM_TCP_NODELAY] = "TFE_CMSG_UPSTREAM_TCP_NODELAY"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_UPSTREAM_TCP_TTL] = "TFE_CMSG_UPSTREAM_TCP_TTL"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_UPSTREAM_TCP_KEEPALIVE] = "TFE_CMSG_UPSTREAM_TCP_KEEPALIVE"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_UPSTREAM_TCP_KEEPCNT] = "TFE_CMSG_UPSTREAM_TCP_KEEPCNT"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_UPSTREAM_TCP_KEEPIDLE] = "TFE_CMSG_UPSTREAM_TCP_KEEPIDLE"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_UPSTREAM_TCP_KEEPINTVL] = "TFE_CMSG_UPSTREAM_TCP_KEEPINTVL"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_UPSTREAM_TCP_USER_TIMEOUT] = "TFE_CMSG_UPSTREAM_TCP_USER_TIMEOUT"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_TCP_PASSTHROUGH] = "TFE_CMSG_TCP_PASSTHROUGH"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_SRC_SUB_ID] = "TFE_CMSG_SRC_SUB_ID"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_DST_SUB_ID] = "TFE_CMSG_DST_SUB_ID"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_SRC_ASN] = "TFE_CMSG_SRC_ASN"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_DST_ASN] = "TFE_CMSG_DST_ASN"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_SRC_ORGANIZATION] = "TFE_CMSG_SRC_ORGANIZATION"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_DST_ORGANIZATION] = "TFE_CMSG_DST_ORGANIZATION"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_SRC_IP_LOCATION_COUNTRY] = "TFE_CMSG_SRC_IP_LOCATION_COUNTRY"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_DST_IP_LOCATION_COUNTRY] = "TFE_CMSG_DST_IP_LOCATION_COUNTRY"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_SRC_IP_LOCATION_PROVINE] = "TFE_CMSG_SRC_IP_LOCATION_PROVINE"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_DST_IP_LOCATION_PROVINE] = "TFE_CMSG_DST_IP_LOCATION_PROVINE"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_SRC_IP_LOCATION_CITY] = "TFE_CMSG_SRC_IP_LOCATION_CITY"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_DST_IP_LOCATION_CITY] = "TFE_CMSG_DST_IP_LOCATION_CITY"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_SRC_IP_LOCATION_SUBDIVISION] = "TFE_CMSG_SRC_IP_LOCATION_SUBDIVISION"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_DST_IP_LOCATION_SUBDIVISION] = "TFE_CMSG_DST_IP_LOCATION_SUBDIVISION"; + + tfe_cmsg_tlv_type_to_string[TFE_CMSG_SSL_CLIENT_JA3_FINGERPRINT] = "TFE_CMSG_SSL_CLIENT_JA3_FINGERPRINT"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_FQDN_CAT_ID_NUM] = "TFE_CMSG_FQDN_CAT_ID_NUM"; + tfe_cmsg_tlv_type_to_string[TFE_CMSG_FQDN_CAT_ID_VAL] = "TFE_CMSG_FQDN_CAT_ID_VAL"; + + tfe_cmsg_tlv_type_to_string[TFE_CMSG_COMMON_DIRECTION] = "TFE_CMSG_COMMON_DIRECTION"; + + tfe_cmsg_tlv_type_to_string[TFE_CMSG_SSL_PASSTHROUGH_REASON] = "TFE_CMSG_SSL_PASSTHROUGH_REASON"; +} diff --git a/common/src/tfe_metrics.cpp b/common/src/tfe_metrics.cpp new file mode 100644 index 0000000..735a0a8 --- /dev/null +++ b/common/src/tfe_metrics.cpp @@ -0,0 +1,133 @@ +#include +#include +#include +#include + +#include "tfe_proxy.h" +#include "tfe_metrics.h" + +enum SCE_STAT_FIELD +{ + // hit block policy + STAT_HIT_POLICY_PKT, + STAT_HIT_POLICY_B, + + // dev nf interface + STAT_RAW_PKT_RX_PKT, + STAT_RAW_PKT_RX_B, + + STAT_RAW_PKT_TX_PKT, + STAT_RAW_PKT_TX_B, + + // steering + STAT_DECRYPTED_TX_PKT, + STAT_DECRYPTED_TX_B, + STAT_DECRYPTED_RX_PKT, + STAT_DECRYPTED_RX_B, + + // control packet + STAT_CONTROL_RX_PKT, + STAT_CONTROL_RX_B, + + STAT_CTRL_PKT_OPENING, + STAT_CTRL_PKT_ACTIVE, + STAT_CTRL_PKT_CLOSING, + STAT_CTRL_PKT_RESETALL, + STAT_CTRL_PKT_ERROR, + + // send log + STAT_SEND_LOG, + + // max + STAT_MAX, +}; + +static const char *stat_map[] = +{ + // hit policy + [STAT_HIT_POLICY_PKT] = "hit_policy_pkt", + [STAT_HIT_POLICY_B] = "hit_policy_B", + + // dev nf interface + [STAT_RAW_PKT_RX_PKT] = "raw_rx_pkt", + [STAT_RAW_PKT_RX_B] = "raw_rx_B", + + [STAT_RAW_PKT_TX_PKT] = "raw_tx_pkt", + [STAT_RAW_PKT_TX_B] = "raw_tx_B", + + // decrypted + [STAT_DECRYPTED_TX_PKT] = "decrypt_tx_pkt", + [STAT_DECRYPTED_TX_B] = "decrypt_tx_B", + [STAT_DECRYPTED_RX_PKT] = "decrypt_rx_pkt", + [STAT_DECRYPTED_RX_B] = "decrypt_rx_B", + + // control packet + [STAT_CONTROL_RX_PKT] = "ctrl_rx_pkt", + [STAT_CONTROL_RX_B] = "ctrl_rx_B", + + [STAT_CTRL_PKT_OPENING] = "ctrl_pkt_open", + [STAT_CTRL_PKT_ACTIVE] = "ctrl_pkt_avtive", + [STAT_CTRL_PKT_CLOSING] = "ctrl_pkt_close", + [STAT_CTRL_PKT_RESETALL] = "ctrl_pkt_reset", + [STAT_CTRL_PKT_ERROR] = "ctrl_pkt_error", + + // send log + [STAT_SEND_LOG] = "send_log", + + [STAT_MAX] = NULL +}; + +struct global_metrics *global_metrics_create() +{ + struct global_metrics *metrics = (struct global_metrics *)calloc(1, sizeof(struct global_metrics)); + + metrics->fs_handle=tfe_proxy_get_fs_handle(); + for (int i = 0; i < STAT_MAX; i++) + { + metrics->fs_id[i] = FS_register(metrics->fs_handle, FS_STYLE_FIELD, FS_CALC_CURRENT, stat_map[i]); + } + + return metrics; +} + +void global_metrics_destory(struct global_metrics *metrics) +{ + if (metrics) + { + FS_library_destroy(); + free(metrics); + metrics = NULL; + } +} + +void global_metrics_dump(struct global_metrics *metrics) +{ + FS_operate(metrics->fs_handle, metrics->fs_id[STAT_HIT_POLICY_PKT], 0, FS_OP_SET, __atomic_fetch_add(&(metrics->hit_policy.n_pkts), 0, __ATOMIC_RELAXED)); + FS_operate(metrics->fs_handle, metrics->fs_id[STAT_HIT_POLICY_B], 0, FS_OP_SET, __atomic_fetch_add(&(metrics->hit_policy.n_bytes), 0, __ATOMIC_RELAXED)); + + FS_operate(metrics->fs_handle, metrics->fs_id[STAT_RAW_PKT_RX_PKT], 0, FS_OP_SET, __atomic_fetch_add(&(metrics->raw_pkt_rx.n_pkts), 0, __ATOMIC_RELAXED)); + FS_operate(metrics->fs_handle, metrics->fs_id[STAT_RAW_PKT_RX_B], 0, FS_OP_SET, __atomic_fetch_add(&(metrics->raw_pkt_rx.n_bytes), 0, __ATOMIC_RELAXED)); + + FS_operate(metrics->fs_handle, metrics->fs_id[STAT_RAW_PKT_TX_PKT], 0, FS_OP_SET, __atomic_fetch_add(&(metrics->raw_pkt_tx.n_pkts), 0, __ATOMIC_RELAXED)); + FS_operate(metrics->fs_handle, metrics->fs_id[STAT_RAW_PKT_TX_B], 0, FS_OP_SET, __atomic_fetch_add(&(metrics->raw_pkt_tx.n_bytes), 0, __ATOMIC_RELAXED)); + + + FS_operate(metrics->fs_handle, metrics->fs_id[STAT_DECRYPTED_TX_PKT], 0, FS_OP_SET, __atomic_fetch_add(&(metrics->decrypt_tx.n_pkts), 0, __ATOMIC_RELAXED)); + FS_operate(metrics->fs_handle, metrics->fs_id[STAT_DECRYPTED_TX_B], 0, FS_OP_SET, __atomic_fetch_add(&(metrics->decrypt_tx.n_bytes), 0, __ATOMIC_RELAXED)); + + FS_operate(metrics->fs_handle, metrics->fs_id[STAT_DECRYPTED_RX_PKT], 0, FS_OP_SET, __atomic_fetch_add(&(metrics->decrypt_rx.n_pkts), 0, __ATOMIC_RELAXED)); + FS_operate(metrics->fs_handle, metrics->fs_id[STAT_DECRYPTED_RX_B], 0, FS_OP_SET, __atomic_fetch_add(&(metrics->decrypt_rx.n_bytes), 0, __ATOMIC_RELAXED)); + + // 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_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)); + FS_operate(metrics->fs_handle, metrics->fs_id[STAT_CTRL_PKT_CLOSING], 0, FS_OP_SET, __atomic_fetch_add(&(metrics->ctrl_pkt_closing_num), 0, __ATOMIC_RELAXED)); + FS_operate(metrics->fs_handle, metrics->fs_id[STAT_CTRL_PKT_RESETALL], 0, FS_OP_SET, __atomic_fetch_add(&(metrics->ctrl_pkt_resetall_num), 0, __ATOMIC_RELAXED)); + FS_operate(metrics->fs_handle, metrics->fs_id[STAT_CTRL_PKT_ERROR], 0, FS_OP_SET, __atomic_fetch_add(&(metrics->ctrl_pkt_error_num), 0, __ATOMIC_RELAXED)); + + // send log + FS_operate(metrics->fs_handle, metrics->fs_id[STAT_SEND_LOG], 0, FS_OP_SET, __atomic_fetch_add(&(metrics->send_log), 0, __ATOMIC_RELAXED)); +} diff --git a/common/src/tfe_mpack.cpp b/common/src/tfe_mpack.cpp new file mode 100644 index 0000000..73a7b89 --- /dev/null +++ b/common/src/tfe_mpack.cpp @@ -0,0 +1,216 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "tfe_cmsg.h" +#include "tfe_utils.h" +#include "tfe_ctrl_packet.h" + +enum ctr_pkt_index +{ + INDEX_TSYNC = 0, + INDEX_SESSION_ID, + INDEX_STATE, + INDEX_METHOD, + INDEX_SCE, + INDEX_SHAPER, + INDEX_PROXY, + INDEX_MAX +}; + +struct mpack_mmap_id2type +{ + int id; + enum tfe_cmsg_tlv_type type; + char *str_name; + int size; +}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}, + {.id = 2, .type = TFE_CMSG_TCP_RESTORE_ACK, .str_name = "TFE_CMSG_TCP_RESTORE_ACK", .size = 4}, + {.id = 3, .type = TFE_CMSG_TCP_RESTORE_MSS_CLIENT, .str_name = "TFE_CMSG_TCP_RESTORE_MSS_CLIENT", .size = 2}, + {.id = 4, .type = TFE_CMSG_TCP_RESTORE_MSS_SERVER, .str_name = "TFE_CMSG_TCP_RESTORE_MSS_SERVER", .size = 2}, + {.id = 5, .type = TFE_CMSG_TCP_RESTORE_WSACLE_CLIENT, .str_name = "TFE_CMSG_TCP_RESTORE_WSACLE_CLIENT", .size = 1}, + {.id = 6, .type = TFE_CMSG_TCP_RESTORE_WSACLE_SERVER, .str_name = "TFE_CMSG_TCP_RESTORE_WSACLE_SERVER", .size = 1}, + {.id = 7, .type = TFE_CMSG_TCP_RESTORE_SACK_CLIENT, .str_name = "TFE_CMSG_TCP_RESTORE_SACK_CLIENT", .size = 1}, + {.id = 8, .type = TFE_CMSG_TCP_RESTORE_SACK_SERVER, .str_name = "TFE_CMSG_TCP_RESTORE_SACK_SERVER", .size = 1}, + {.id = 9, .type = TFE_CMSG_TCP_RESTORE_TS_CLIENT, .str_name = "TFE_CMSG_TCP_RESTORE_TS_CLIENT", .size = 1}, + {.id = 10, .type = TFE_CMSG_TCP_RESTORE_TS_SERVER, .str_name = "TFE_CMSG_TCP_RESTORE_TS_SERVER", .size = 1}, + {.id = 11, .type = TFE_CMSG_TCP_RESTORE_PROTOCOL, .str_name = "TFE_CMSG_TCP_RESTORE_PROTOCOL", .size = 1}, + {.id = 12, .type = TFE_CMSG_TCP_RESTORE_WINDOW_CLIENT, .str_name = "TFE_CMSG_TCP_RESTORE_WINDOW_CLIENT", .size = 2}, + {.id = 13, .type = TFE_CMSG_TCP_RESTORE_WINDOW_SERVER, .str_name = "TFE_CMSG_TCP_RESTORE_WINDOW_SERVER", .size = 2}, + {.id = 14, .type = TFE_CMSG_TCP_RESTORE_TS_CLIENT_VAL, .str_name = "TFE_CMSG_TCP_RESTORE_TS_CLIENT_VAL", .size = 4}, + {.id = 15, .type = TFE_CMSG_TCP_RESTORE_TS_SERVER_VAL, .str_name = "TFE_CMSG_TCP_RESTORE_TS_SERVER_VAL", .size = 4}, + {.id = 16, .type = TFE_CMSG_TCP_RESTORE_INFO_PACKET_CUR_DIR, .str_name = "TFE_CMSG_TCP_RESTORE_INFO_PACKET_CUR_DIR", .size = 1}, + {.id = 17, .type = TFE_CMSG_SRC_SUB_ID, .str_name = "TFE_CMSG_SRC_SUB_ID", .size = 256}, + {.id = 18, .type = TFE_CMSG_DST_SUB_ID, .str_name = "TFE_CMSG_DST_SUB_ID", .size = 256}, + {.id = 19, .type = TFE_CMSG_SRC_ASN, .str_name = "TFE_CMSG_SRC_ASN", .size = 64}, + {.id = 20, .type = TFE_CMSG_DST_ASN, .str_name = "TFE_CMSG_DST_ASN", .size = 64}, + {.id = 21, .type = TFE_CMSG_SRC_ORGANIZATION, .str_name = "TFE_CMSG_SRC_ORGANIZATION", .size = 256}, + {.id = 22, .type = TFE_CMSG_DST_ORGANIZATION, .str_name = "TFE_CMSG_DST_ORGANIZATION", .size = 256}, + {.id = 23, .type = TFE_CMSG_SRC_IP_LOCATION_COUNTRY, .str_name = "TFE_CMSG_SRC_IP_LOCATION_COUNTRY", .size = 256}, + {.id = 24, .type = TFE_CMSG_DST_IP_LOCATION_COUNTRY, .str_name = "TFE_CMSG_DST_IP_LOCATION_COUNTRY", .size = 256}, + {.id = 25, .type = TFE_CMSG_SRC_IP_LOCATION_PROVINE, .str_name = "TFE_CMSG_SRC_IP_LOCATION_PROVINE", .size = 256}, + {.id = 26, .type = TFE_CMSG_DST_IP_LOCATION_PROVINE, .str_name = "TFE_CMSG_DST_IP_LOCATION_PROVINE", .size = 256}, + {.id = 27, .type = TFE_CMSG_SRC_IP_LOCATION_CITY, .str_name = "TFE_CMSG_SRC_IP_LOCATION_CITY", .size = 256}, + {.id = 28, .type = TFE_CMSG_DST_IP_LOCATION_CITY, .str_name = "TFE_CMSG_DST_IP_LOCATION_CITY", .size = 256}, + {.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} +}; + +static int proxy_parse_messagepack(msgpack_object obj, void *ctx) +{ + 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]; + + 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; + } + 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); + 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]); + } + } + continue; + } + + 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); + 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); + 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)); + } + break; + default: + break; + } + } + return 0; +} + +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_unpacked unpacked; + msgpack_unpacked_init(&unpacked); + + 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; + } + + 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; + } + + for (unsigned int i = 0; i < obj.via.array.size; i++) { + msgpack_object 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); + } + else { + 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); + } + else { + 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) + { + handler->state = SESSION_STATE_OPENING; + } + 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) + { + handler->state = SESSION_STATE_CLOSING; + } + 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); + } + } + else { + 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); + } + else { + 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]; + 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) { + 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); + } + break; + default: + break; + } + } + return 0; +} diff --git a/common/src/tfe_packet_io.cpp b/common/src/tfe_packet_io.cpp new file mode 100644 index 0000000..e3fb77b --- /dev/null +++ b/common/src/tfe_packet_io.cpp @@ -0,0 +1,1553 @@ +#include +#include +#include +#include + +#include +#include +#include + +#include +// #include +#include +#include "tfe_acceptor_kni.h" +#include "tfe_ctrl_packet.h" +#include "tfe_raw_packet.h" +#include "io_uring.h" +#include "tfe_metrics.h" +#include "tfe_cmsg.h" +#include "tfe_tcp_restore.h" +#include "tfe_stream.h" +#include "raw_socket.h" +#include "packet_construct.h" +#include "tfe_tap_rss.h" +#include + +/* + * add: vxlan_hdr + * del: marsio_buff_ctrlzone_reset() + * +----+ NF2SF +----+ + * | |--------------------------->| | + * | | | | + * | |-------+ | |-------+ + * | NF | | NF2NF (undo) | SF | | SF2SF (del old vxlan_hdr; add new vxlan_hdr) + * | |<------+ | |<------+ + * | | | | + * | |<---------------------------| | + * | | SF2NF | | + * +---+ del: vxlan_hdr +----+ + * add: session_id + route_ctx + sid + */ + +/****************************************************************************** + * Struct + ******************************************************************************/ + +#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) + +struct config +{ + int bypass_all_traffic; + int rx_burst_max; + char app_symbol[256]; + char dev_nf_interface[256]; +}; + +struct device +{ + struct mr_vdev *mr_dev; + struct mr_sendpath *mr_path; +}; + +struct packet_io +{ + int thread_num; + struct mr_instance *instance; + struct device dev_nf_interface; + struct config config; +}; + +enum raw_pkt_action +{ + RAW_PKT_ERR_BYPASS, + RAW_PKT_HIT_BYPASS, + RAW_PKT_HIT_BLOCK, + RAW_PKT_HIT_STEERING, + RAW_PKT_HIT_MIRRORING, +}; + +enum inject_pkt_action +{ + INJT_PKT_ERR_DROP, + INJT_PKT_MIRR_RX_DROP, + INJT_PKT_HIT_BLOCK, + INJT_PKT_HIT_FWD2SF, // forward to service function + INJT_PKT_HIT_FWD2NF, // forward to network function +}; + +struct metadata +{ + uint64_t session_id; + + char *raw_data; + int raw_len; + + int dir_is_e2i; + 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) + + 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; + uint16_t mss_value; +} __attribute__((__packed__)); + +struct tcp_option_window_scale { + uint8_t kind; + uint8_t length; + uint8_t shift_count; +} __attribute__((__packed__)); + +struct tcp_option_sack { + uint8_t kind; + uint8_t length; +} __attribute__((__packed__)); + +struct tcp_option_time_stamp { + uint8_t kind; + uint8_t length; + uint32_t tsval; + uint32_t tsecr; +} __attribute__((__packed__)); + +static int fake_tcp_handshake(struct tfe_proxy *proxy, struct tcp_restore_info *restore_info) +{ + char buffer[1500] = {0}; + int length = 0; + + char tcp_option_buffer_c[40] = {0}; + char tcp_option_buffer_s[40] = {0}; + char tcp_option_buffer_c2[40] = {0}; + int tcp_option_length_c = 0; + int tcp_option_length_s = 0; + int tcp_option_length_c2 = 0; + + const struct tcp_restore_endpoint *client = &restore_info->client; + const struct tcp_restore_endpoint *server = &restore_info->server; + struct raw_socket *raw_socket_c = raw_socket_create(proxy->traffic_steering_options.device_client, proxy->traffic_steering_options.so_mask_client); + struct raw_socket *raw_socket_s = raw_socket_create(proxy->traffic_steering_options.device_server, proxy->traffic_steering_options.so_mask_server); + if (raw_socket_c == NULL || raw_socket_s == NULL) + { + raw_socket_destory(raw_socket_c); + raw_socket_destory(raw_socket_s); + + return -1; + } + + uint32_t c_seq = client->seq - 1; + uint32_t s_seq = server->seq - 1; + + /* + * Maximum segment size: Kind: 2, Length: 4 + * +---------+---------+---------+ + * | Kind=2 |Length=4 |mss.value| + * +---------+---------+---------+ + * 1 1 2 + */ + if (client->mss && server->mss) + { + struct tcp_option_mss *option_c = (struct tcp_option_mss *)(tcp_option_buffer_c + tcp_option_length_c); + option_c->kind = 2; + option_c->length = 4; + option_c->mss_value = htons(client->mss); + tcp_option_length_c += sizeof(struct tcp_option_mss); + + struct tcp_option_mss *option_s = (struct tcp_option_mss *)(tcp_option_buffer_s + tcp_option_length_s); + option_s->kind = 2; + option_s->length = 4; + option_s->mss_value = htons(server->mss); + tcp_option_length_s += sizeof(struct tcp_option_mss); + } + + /* + * Window Scale option: Kind: 3, Length: 3 + * +---------+---------+---------+ + * | Kind=3 |Length=3 |shift.cnt| + * +---------+---------+---------+ + * 1 1 1 + */ + if (client->wscale_perm && server->wscale_perm) + { + // padding + memset(tcp_option_buffer_c + tcp_option_length_c, 1, 1); + tcp_option_length_c += 1; + memset(tcp_option_buffer_s + tcp_option_length_s, 1, 1); + tcp_option_length_s += 1; + + struct tcp_option_window_scale *option_c = (struct tcp_option_window_scale *)(tcp_option_buffer_c + tcp_option_length_c); + option_c->kind = 3; + option_c->length = 3; + option_c->shift_count = client->wscale; + tcp_option_length_c += sizeof(struct tcp_option_window_scale); + + struct tcp_option_window_scale *option_s = (struct tcp_option_window_scale *)(tcp_option_buffer_s + tcp_option_length_s); + option_s->kind = 3; + option_s->length = 3; + option_s->shift_count = server->wscale; + tcp_option_length_s += sizeof(struct tcp_option_window_scale); + } + + /* + * SACK option: Kind: 4, Length: 2 + * +---------+---------+ + * | Kind=4 |Length=2 | + * +---------+---------+ + * 1 1 + */ + if (client->sack_perm && server->sack_perm) + { + // padding + memset(tcp_option_buffer_c + tcp_option_length_c, 1, 2); + tcp_option_length_c += 2; + memset(tcp_option_buffer_s + tcp_option_length_s, 1, 2); + tcp_option_length_s += 2; + + struct tcp_option_sack *option_c = (struct tcp_option_sack *)(tcp_option_buffer_c + tcp_option_length_c); + option_c->kind = 4; + option_c->length = 2; + tcp_option_length_c += sizeof(struct tcp_option_sack); + + struct tcp_option_sack *option_s = (struct tcp_option_sack *)(tcp_option_buffer_s + tcp_option_length_s); + option_s->kind = 4; + option_s->length = 2; + tcp_option_length_s += sizeof(struct tcp_option_sack); + } + + /* + * Time Stamp option: Kind: 8, Length: 10 + * +---------+---------+-----+-----+ + * | Kind=8 |Length=10|tsval|tsecr| + * +---------+---------+-----+-----+ + * 1 1 4 4 + */ + if (client->timestamp_perm && server->timestamp_perm) + { + // padding + memset(tcp_option_buffer_c + tcp_option_length_c, 1, 2); + tcp_option_length_c += 2; + memset(tcp_option_buffer_s + tcp_option_length_s, 1, 2); + tcp_option_length_s += 2; + memset(tcp_option_buffer_c2 + tcp_option_length_c2, 1, 2); + tcp_option_length_c2 += 2; + + struct tcp_option_time_stamp *option_c = (struct tcp_option_time_stamp *)(tcp_option_buffer_c + tcp_option_length_c); + option_c->kind = 8; + option_c->length = 10; + option_c->tsval = htonl(client->ts_val); + option_c->tsecr = htonl(0); + tcp_option_length_c += sizeof(struct tcp_option_time_stamp); + + struct tcp_option_time_stamp *option_s = (struct tcp_option_time_stamp *)(tcp_option_buffer_s + tcp_option_length_s); + option_s->kind = 8; + option_s->length = 10; + option_s->tsval = htonl(server->ts_val); + option_s->tsecr = htonl(client->ts_val); + tcp_option_length_s += sizeof(struct tcp_option_time_stamp); + + struct tcp_option_time_stamp *option_c2 = (struct tcp_option_time_stamp *)(tcp_option_buffer_c2 + tcp_option_length_c2); + option_c2->kind = 8; + option_c2->length = 10; + option_c2->tsval = htonl(client->ts_val); + option_c2->tsecr = htonl(server->ts_val); + tcp_option_length_c2 += sizeof(struct tcp_option_time_stamp); + } + + if (client->addr.ss_family == AF_INET6) + { + struct sockaddr_in6 *sk_client = (struct sockaddr_in6 *)&client->addr; + struct sockaddr_in6 *sk_server = (struct sockaddr_in6 *)&server->addr; + uint16_t port_client = sk_client->sin6_port; + uint16_t port_server = sk_server->sin6_port; + + // C -> S + length = tcp_packet_v6_construct( + buffer, // buffer + &raw_socket_c->mac_addr, &raw_socket_s->mac_addr, 0, ETH_P_IPV6, // Ether + &sk_client->sin6_addr, &sk_server->sin6_addr, 55, // IPv6 + port_client, port_server, c_seq, 0, TCP_SYN_FLAG, client->window, // TCP Header + tcp_option_buffer_c, tcp_option_length_c, // TCP Options + NULL, 0); // Payload + raw_socket_send(raw_socket_c, buffer, length); + c_seq += 1; + + // S -> C + length = tcp_packet_v6_construct( + buffer, // buffer + &raw_socket_s->mac_addr, &raw_socket_c->mac_addr, 0, ETH_P_IPV6, // Ether + &sk_server->sin6_addr, &sk_client->sin6_addr, 65, // IPv6 + port_server, port_client, s_seq, c_seq, TCP_SYN_FLAG | TCP_ACK_FLAG, server->window, // TCP Header + tcp_option_buffer_s, tcp_option_length_s, // TCP Options + NULL, 0); // Payload + raw_socket_send(raw_socket_s, buffer, length); + s_seq += 1; + + // C -> S + length = tcp_packet_v6_construct( + buffer, // buffer + &raw_socket_c->mac_addr, &raw_socket_s->mac_addr, 0, ETH_P_IPV6, // Ether + &sk_client->sin6_addr, &sk_server->sin6_addr, 55, // IPv6 + port_client, port_server, c_seq, s_seq, TCP_ACK_FLAG, client->window, // TCP Header + tcp_option_buffer_c2, tcp_option_length_c2, // TCP Options + NULL, 0); // Payload + raw_socket_send(raw_socket_c, buffer, length); + } + else + { + struct sockaddr_in *sk_client = (struct sockaddr_in *)&client->addr; + struct sockaddr_in *sk_server = (struct sockaddr_in *)&server->addr; + uint16_t port_client = sk_client->sin_port; + uint16_t port_server = sk_server->sin_port; + + // C -> S + length = tcp_packet_v4_construct( + buffer, // buffer + &raw_socket_c->mac_addr, &raw_socket_s->mac_addr, 0, ETH_P_IP, // Ether + &sk_client->sin_addr, &sk_server->sin_addr, 0, 55, 0x11, // IPv4 + port_client, port_server, c_seq, 0, TCP_SYN_FLAG, client->window, // TCP Header + tcp_option_buffer_c, tcp_option_length_c, // TCP Options + NULL, 0); + raw_socket_send(raw_socket_c, buffer, length); + c_seq += 1; + + // S -> C + length = tcp_packet_v4_construct( + buffer, // buffer + &raw_socket_s->mac_addr, &raw_socket_c->mac_addr, 0, ETH_P_IP, // Ether + &sk_server->sin_addr,&sk_client->sin_addr, 0, 65, 0x12, // IPv4 + port_server, port_client, s_seq, c_seq, TCP_SYN_FLAG | TCP_ACK_FLAG, server->window, // TCP Header + tcp_option_buffer_s, tcp_option_length_s, // TCP Options + NULL, 0); + raw_socket_send(raw_socket_s, buffer, length); + s_seq += 1; + + // C -> S + length = tcp_packet_v4_construct( + buffer, // buffer + &raw_socket_c->mac_addr, &raw_socket_s->mac_addr, 0, ETH_P_IP, // Ether + &sk_client->sin_addr, &sk_server->sin_addr, 0, 55, 0x13, // IPv4 + port_client, port_server, c_seq, s_seq, TCP_ACK_FLAG, client->window, // TCP Header + tcp_option_buffer_c2, tcp_option_length_c2, // TCP Options + NULL, 0); + raw_socket_send(raw_socket_c, buffer, length); + } + + raw_socket_destory(raw_socket_c); + raw_socket_destory(raw_socket_s); + + return 0; +} + +static int overwrite_tcp_mss(struct tfe_cmsg *cmsg, struct tcp_restore_info *restore) +{ + int ret = 0; + uint16_t size = 0; + int server_side_mss_enable = 0; + int server_side_mss_value = 0; + int client_side_mss_enable = 0; + int client_side_mss_value = 0; + + ret = tfe_cmsg_get_value(cmsg, TFE_CMSG_DOWNSTREAM_TCP_MSS_ENABLE, (unsigned char *)&client_side_mss_enable, sizeof(client_side_mss_enable), &size); + if (ret < 0) + { + TFE_LOG_ERROR(g_default_logger, "failed at fetch client side tcp mss from cmsg: %s", strerror(-ret)); + return -1; + } + ret = tfe_cmsg_get_value(cmsg, TFE_CMSG_DOWNSTREAM_TCP_MSS_VALUE, (unsigned char *)&client_side_mss_value, sizeof(client_side_mss_value), &size); + if (ret < 0) + { + TFE_LOG_ERROR(g_default_logger, "failed at fetch client side tcp mss value from cmsg: %s", strerror(-ret)); + return -1; + } + ret = tfe_cmsg_get_value(cmsg, TFE_CMSG_UPSTREAM_TCP_MSS_ENABLE, (unsigned char *)&server_side_mss_enable, sizeof(server_side_mss_enable), &size); + if (ret < 0) + { + TFE_LOG_ERROR(g_default_logger, "failed at fetch server side tcp mss from cmsg: %s", strerror(-ret)); + return -1; + } + ret = tfe_cmsg_get_value(cmsg, TFE_CMSG_UPSTREAM_TCP_MSS_VALUE, (unsigned char *)&server_side_mss_value, sizeof(server_side_mss_value), &size); + if (ret < 0) + { + TFE_LOG_ERROR(g_default_logger, "failed at fetch server side tcp mss value from cmsg: %s", strerror(-ret)); + return -1; + } + if (client_side_mss_enable) + { + restore->client.mss = client_side_mss_value; + } + if (server_side_mss_enable) + { + restore->server.mss = server_side_mss_value; + } + + 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) +{ + 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; + + 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) + { + 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); + } + + 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]); + } + + tcp_restore_set_from_cmsg(parser->cmsg, &restore_info); + tcp_restore_set_from_pkg(&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 + TFE_LOG_DEBUG(g_default_logger, "restore_info: client"); + + 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: 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"); + + + 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, "Failed at fake_tcp_handshake()"); + 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, "Failed at tcp_restore_fd_create(fd_fake_c)"); + 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, "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); + + return 0; +end: + return -1; +} + +/* +{ + "tsync": "1.0", + "session_id": "123456789", + "state": "active", + "method": "log_update", + "params": { + "sf_profile_ids": [ + 2, + 3, + 4, + 5, + 6, + 7 + ] + } +} +*/ +static void send_event_log(struct session_ctx *s_ctx, 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 packet_io *packet_io = thread->ref_io; + + char buffer[32] = {0}; + sprintf(buffer, "%lu", s_ctx->session_id); + + 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(); + + cJSON *params = cJSON_CreateObject(); + cJSON_AddItemToObject(params, "sf_profile_ids", sf_profile_ids); + cJSON_AddItemToObject(root, "params", params); + char *json_str = cJSON_PrintUnformatted(root); + + 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); + + 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 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); + + free(json_str); + cJSON_Delete(root); +} + +// return 0 : success +// return -1 : error +static int handle_session_closing(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 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); + + send_event_log(s_ctx, thread_seq, ctx); + + session_table_delete_by_id(thread->session_table, meta->session_id); + return 0; + } + + 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) +{ + struct acceptor_thread_ctx *thread = (struct acceptor_thread_ctx *)ctx; + struct acceptor_ctx *acceptor_ctx = thread->ref_acceptor_ctx; + + TFE_LOG_ERROR(g_default_logger, "%s: session %lu resetall: notification clears all session tables !!!", LOG_TAG_PKTIO, meta->session_id); + + for (int i = 0; i < acceptor_ctx->nr_worker_threads; i++) + { + struct acceptor_thread_ctx *thread_ctx = &acceptor_ctx->work_threads[i]; + __atomic_fetch_add(&thread_ctx->session_table_need_reset, 1, __ATOMIC_RELAXED); + } + + return 0; +} + +static void session_value_free_cb(void *ctx) +{ + struct session_ctx *s_ctx = (struct session_ctx *)ctx; + session_ctx_free(s_ctx); +} + +// 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; + } +} diff --git a/common/src/tfe_raw_packet.cpp b/common/src/tfe_raw_packet.cpp new file mode 100644 index 0000000..a407669 --- /dev/null +++ b/common/src/tfe_raw_packet.cpp @@ -0,0 +1,994 @@ +#include +#include +#include +#include + +#include +#include +#define __FAVOR_BSD 1 +#include +#include +#include + +#include "tfe_utils.h" +#include "uthash.h" +#include "tfe_addr_tuple4.h" +#include "tfe_raw_packet.h" + +/****************************************************************************** + * Struct + ******************************************************************************/ + +enum parse_status +{ + PARSE_STATUS_CONTINUE, + PARSE_STATUS_STOP +}; + +struct vlan_hdr +{ + uint16_t vlan_cfi; + uint16_t protocol; +} __attribute__((__packed__)); + +struct vxlan_hdr +{ + uint8_t flags[2]; + uint16_t gdp; // group policy id + uint8_t vni[3]; + uint8_t reserved; +} __attribute__((__packed__)); + +struct gtp_hdr +{ +#if __BYTE_ORDER == __LITTLE_ENDIAN + unsigned char flags; + unsigned char msg_type; + unsigned short len; + unsigned int teid; +#elif __BYTE_ORDER == __BIG_ENDIAN + unsigned int teid; + unsigned short len; + unsigned char msg_type; + unsigned char flags; +#else +#error "Please check " +#endif +} __attribute__((__packed__)); + +#define GTP_HDR_VER_MASK (0xE0) +#define GTP_HDR_FLAG_N_PDU (0x01) +#define GTP_HDR_FLAG_SEQ_NUM (0x02) +#define GTP_HDR_FLAG_NEXT_EXT_HDR (0x04) + +/****************************************************************************** + * Static API + ******************************************************************************/ + +static int raw_packet_parser_push(struct raw_pkt_parser *handler, enum layer_type type, uint16_t offset); +static enum parse_status raw_packet_parser_status(struct raw_pkt_parser *handler, const void *data, enum layer_type this_type); + +static const char *ldbc_method_to_string(enum ldbc_method ldbc_method); + +// parser utils +static void set_addr_tuple4(const void *data, enum layer_type layer_type, struct addr_tuple4 *addr); +static const char *layer_type2str(enum layer_type this_type); +static uint16_t parse_gtphdr_len(const struct gtp_hdr *gtph); + +// parser protocol +static const void *parse_ether(struct raw_pkt_parser *handler, const void *data, size_t length, enum layer_type this_type); +static const void *parse_ipv4(struct raw_pkt_parser *handler, const void *data, size_t length, enum layer_type this_type); +static const void *parse_ipv6(struct raw_pkt_parser *handler, const void *data, size_t length, enum layer_type this_type); +static const void *parse_tcp(struct raw_pkt_parser *handler, const void *data, size_t length, enum layer_type this_type); +static const void *parse_udp(struct raw_pkt_parser *handler, const void *data, size_t length, enum layer_type this_type); +static const void *parse_pppoe_ses(struct raw_pkt_parser *handler, const void *data, size_t length, enum layer_type this_type); +static const void *parse_vxlan(struct raw_pkt_parser *handler, const void *data, size_t length, enum layer_type this_type); +static const void *parse_vlan8021q(struct raw_pkt_parser *handler, const void *data, size_t length, enum layer_type this_type); +static const void *parse_gtpv1_u(struct raw_pkt_parser *handler, const void *data, size_t length, enum layer_type this_type); +static const void *parse_mpls(struct raw_pkt_parser *handler, const void *data, size_t length, enum layer_type this_type); + +/****************************************************************************** + * Public API + ******************************************************************************/ + +void raw_packet_parser_init(struct raw_pkt_parser *handler, uint64_t pkt_trace_id, enum layer_type expect_type, uint16_t expect_results_num) +{ + memset(handler, 0, sizeof(struct raw_pkt_parser)); + + handler->expect_type = expect_type; + handler->results.layers_used = 0; + handler->results.layers_size = MIN(expect_results_num, (sizeof(handler->results.layers) / sizeof(handler->results.layers[0]))); + handler->ptr_pkt_start = NULL; + handler->pkt_trace_id = pkt_trace_id; +} + +// return most inner payload +const void *raw_packet_parser_parse(struct raw_pkt_parser *handler, const void *data, size_t length) +{ + handler->ptr_pkt_start = data; + + // TESTED + return parse_ether(handler, data, length, LAYER_TYPE_ETHER); +} + +// return 0 : success +// return -1 : error +int raw_packet_parser_get_most_inner_tuple4(struct raw_pkt_parser *handler, struct addr_tuple4 *addr) +{ + const char *l3_layer_data = NULL; + const char *l4_layer_data = NULL; + const struct layer_result *l3_layer_result = NULL; + const struct layer_result *l4_layer_result = NULL; + struct layer_results *results = &handler->results; + + // search L4 layer and L3 layer in reverse order + for (int8_t i = results->layers_used - 1; i >= 0; i--) + { + const struct layer_result *layer = &results->layers[i]; + enum layer_type type = layer->type; + + TFE_LOG_DEBUG(g_default_logger, "%s: find most inner tuple4, pkt_trace_id: %lu, layer[%d/%d]: %s", LOG_TAG_RAWPKT, handler->pkt_trace_id, i, results->layers_size, layer_type2str(type)); + + // first get L4 layer + if (type & LAYER_TYPE_L4) + { + l4_layer_result = layer; + continue; + } + + // second get L3 layer + if (type & LAYER_TYPE_L3) + { + l3_layer_result = layer; + break; + } + } + + if (l3_layer_result) + { + l3_layer_data = (const char *)handler->ptr_pkt_start + l3_layer_result->offset; + set_addr_tuple4(l3_layer_data, l3_layer_result->type, addr); + } + + if (l4_layer_result) + { + l4_layer_data = (const char *)handler->ptr_pkt_start + l4_layer_result->offset; + set_addr_tuple4(l4_layer_data, l4_layer_result->type, addr); + } + + if (l3_layer_result && l4_layer_result) + { + return 0; + } + else + { + return -1; + } +} + +// return 0 : success +// return -1 : error +int raw_packet_parser_get_most_outer_tuple4(struct raw_pkt_parser *handler, struct addr_tuple4 *addr) +{ + const char *l3_layer_data = NULL; + const char *l4_layer_data = NULL; + const struct layer_result *l3_layer_result = NULL; + const struct layer_result *l4_layer_result = NULL; + struct layer_results *results = &handler->results; + + // search L3 layer and L4 layer in order + for (int8_t i = 0; i <= results->layers_used - 1; i++) + { + const struct layer_result *layer = &results->layers[i]; + enum layer_type type = layer->type; + + TFE_LOG_DEBUG(g_default_logger, "%s: find most outer tuple4, pkt_trace_id: %lu, layer[%d/%d]: %s", LOG_TAG_RAWPKT, handler->pkt_trace_id, i, results->layers_size, layer_type2str(type)); + + // first get L3 layer + if (type & LAYER_TYPE_L3) + { + l3_layer_result = layer; + continue; + } + + // second get L4 layer + if (type & LAYER_TYPE_L4) + { + l4_layer_result = layer; + break; + } + } + + if (l3_layer_result) + { + l3_layer_data = (const char *)handler->ptr_pkt_start + l3_layer_result->offset; + set_addr_tuple4(l3_layer_data, l3_layer_result->type, addr); + } + + if (l4_layer_result) + { + l4_layer_data = (const char *)handler->ptr_pkt_start + l4_layer_result->offset; + set_addr_tuple4(l4_layer_data, l4_layer_result->type, addr); + } + + if (l3_layer_result && l4_layer_result) + { + return 0; + } + else + { + return -1; + } +} + +// return 0 : success +// return -1 : error +int raw_packet_parser_get_most_inner_address(struct raw_pkt_parser *handler, struct addr_tuple4 *addr) +{ + const char *l3_layer_data = NULL; + struct layer_results *results = &handler->results; + + // search L3 layer in reverse order + for (int8_t i = results->layers_used - 1; i >= 0; i--) + { + const struct layer_result *layer = &results->layers[i]; + enum layer_type type = layer->type; + + TFE_LOG_DEBUG(g_default_logger, "%s: find most inner address, pkt_trace_id: %lu, layer[%d/%d]: %s", LOG_TAG_RAWPKT, handler->pkt_trace_id, i, results->layers_size, layer_type2str(type)); + if (type & LAYER_TYPE_L3) + { + l3_layer_data = (const char *)handler->ptr_pkt_start + layer->offset; + set_addr_tuple4(l3_layer_data, type, addr); + return 0; + } + } + + return -1; +} + +// return 0 : success +// return -1 : error +int raw_packet_parser_get_most_outer_address(struct raw_pkt_parser *handler, struct addr_tuple4 *addr) +{ + const char *l3_layer_data = NULL; + struct layer_results *results = &handler->results; + + // search L3 layer in order + for (int8_t i = 0; i <= results->layers_used - 1; i++) + { + const struct layer_result *layer = &results->layers[i]; + enum layer_type type = layer->type; + + TFE_LOG_DEBUG(g_default_logger, "%s: find most outer address, pkt_trace_id: %lu, layer[%d/%d]: %s", LOG_TAG_RAWPKT, handler->pkt_trace_id, i, results->layers_size, layer_type2str(type)); + if (type & LAYER_TYPE_L3) + { + l3_layer_data = (const char *)handler->ptr_pkt_start + layer->offset; + set_addr_tuple4(l3_layer_data, type, addr); + return 0; + } + } + + return -1; +} + +uint64_t raw_packet_parser_get_hash_value(struct raw_pkt_parser *handler, enum ldbc_method method, int dir_is_internal) +{ + uint64_t temp = 0; + uint64_t hash_value = 1; + + int inner_addr_len = 0; + int outer_addr_len = 0; + const char *inner_src_addr = NULL; + const char *inner_dst_addr = NULL; + const char *outer_src_addr = NULL; + const char *outer_dst_addr = NULL; + + struct addr_tuple4 inner_addr; + struct addr_tuple4 outer_addr; + memset(&inner_addr, 0, sizeof(inner_addr)); + memset(&outer_addr, 0, sizeof(outer_addr)); + + if (handler == NULL) + { + return hash_value; + } + + if (raw_packet_parser_get_most_inner_address(handler, &inner_addr) == -1) + { + return hash_value; + } + + if (raw_packet_parser_get_most_outer_address(handler, &outer_addr) == -1) + { + return hash_value; + } + + if (inner_addr.addr_type == ADDR_TUPLE4_TYPE_V4) + { + inner_src_addr = (const char *)&(inner_addr.addr_v4.src_addr); + inner_dst_addr = (const char *)&(inner_addr.addr_v4.dst_addr); + inner_addr_len = sizeof(inner_addr.addr_v4.dst_addr); + } + else + { + inner_src_addr = (const char *)&(inner_addr.addr_v6.src_addr); + inner_dst_addr = (const char *)&(inner_addr.addr_v6.dst_addr); + inner_addr_len = sizeof(inner_addr.addr_v6.dst_addr); + } + + if (outer_addr.addr_type == ADDR_TUPLE4_TYPE_V4) + { + outer_src_addr = (const char *)&(outer_addr.addr_v4.src_addr); + outer_dst_addr = (const char *)&(outer_addr.addr_v4.dst_addr); + outer_addr_len = sizeof(outer_addr.addr_v4.dst_addr); + } + else + { + outer_src_addr = (const char *)&(outer_addr.addr_v6.src_addr); + outer_dst_addr = (const char *)&(outer_addr.addr_v6.dst_addr); + outer_addr_len = sizeof(outer_addr.addr_v6.dst_addr); + } + + switch (method) + { + case LDBC_METHOD_HASH_INT_IP: + if (dir_is_internal) + { + // outer src ip + HASH_VALUE(outer_src_addr, outer_addr_len, hash_value); + } + else + { + // outer dst ip + HASH_VALUE(outer_dst_addr, outer_addr_len, hash_value); + } + break; + case LDBC_METHOD_HASH_EXT_IP: + if (dir_is_internal) + { + // outer dst ip + HASH_VALUE(outer_dst_addr, outer_addr_len, hash_value); + } + else + { + // outer src ip + HASH_VALUE(outer_src_addr, outer_addr_len, hash_value); + } + break; + case LDBC_METHOD_HASH_INT_IP_AND_EXT_IP: + // outer dst ip ^ outer src ip + HASH_VALUE(outer_src_addr, outer_addr_len, hash_value); + HASH_VALUE(outer_dst_addr, outer_addr_len, temp); + hash_value = hash_value ^ temp; + break; + case LDBC_METHOD_HASH_INNERMOST_INT_IP: + if (dir_is_internal) + { + // innner src ip + HASH_VALUE(inner_src_addr, inner_addr_len, hash_value); + } + else + { + // innner dst ip + HASH_VALUE(inner_dst_addr, inner_addr_len, hash_value); + } + break; + case LDBC_METHOD_HASH_INNERMOST_EXT_IP: + if (dir_is_internal) + { + // innner dst ip + HASH_VALUE(inner_dst_addr, inner_addr_len, hash_value); + } + else + { + // innner src ip + HASH_VALUE(inner_src_addr, inner_addr_len, hash_value); + } + break; + default: + return hash_value; + } + + char *inner_addr_str = addr_tuple4_to_str(&inner_addr); + char *outer_addr_str = addr_tuple4_to_str(&outer_addr); + TFE_LOG_DEBUG(g_default_logger, "%s: pkt_trace_id: %lu, outer_addr: %s, inner_addr: %s, is_internal: %d, hash_method: %s, hash_value: %lu", + LOG_TAG_RAWPKT, handler->pkt_trace_id, outer_addr_str, inner_addr_str, dir_is_internal, ldbc_method_to_string(method), hash_value); + free(inner_addr_str); + free(outer_addr_str); + + return hash_value; +} + +/****************************************************************************** + * Private API + ******************************************************************************/ + +// return 0 : success +// return -ENOMEM : error +static int raw_packet_parser_push(struct raw_pkt_parser *handler, enum layer_type type, uint16_t offset) +{ + struct layer_results *result = &handler->results; + + if (result->layers_used >= result->layers_size) + { + return -ENOMEM; + } + + result->layers[result->layers_used].offset = offset; + result->layers[result->layers_used].type = type; + result->layers_used++; + + return 0; +} + +// return PARSE_STATUS_CONTINUE +// return PARSE_STATUS_STOP +static enum parse_status raw_packet_parser_status(struct raw_pkt_parser *handler, const void *data, enum layer_type this_type) +{ + /* + * only when this_type & handler->expect_type is true, + * the information of the current layer will be recorded in results. + */ + if (!(this_type & handler->expect_type)) + { + return PARSE_STATUS_CONTINUE; + } + + uint16_t offset = (uintptr_t)data - (uintptr_t)(handler->ptr_pkt_start); + if (raw_packet_parser_push(handler, this_type, offset) < 0) + { + return PARSE_STATUS_STOP; + } + else + { + return PARSE_STATUS_CONTINUE; + } +} + +static const char *ldbc_method_to_string(enum ldbc_method ldbc_method) +{ + switch (ldbc_method) + { + case LDBC_METHOD_HASH_INT_IP: + return "outter_internal_ip"; + case LDBC_METHOD_HASH_EXT_IP: + return "outter_external_ip"; + case LDBC_METHOD_HASH_INT_IP_AND_EXT_IP: + return "outter_internal_ip_and_external_ip"; + case LDBC_METHOD_HASH_INNERMOST_INT_IP: + return "inner_internal_ip"; + case LDBC_METHOD_HASH_INNERMOST_EXT_IP: + return "inner_external_ip"; + default: + return "unknown"; + } +} + +static void set_addr_tuple4(const void *data, enum layer_type layer_type, struct addr_tuple4 *addr) +{ + const struct tcphdr *tcp_hdr = NULL; + const struct udp_hdr *udp_hdr = NULL; + const struct ip *ipv4_hdr = NULL; + const struct ip6_hdr *ipv6_hdr = NULL; + + switch (layer_type) + { + case LAYER_TYPE_TCP: + tcp_hdr = (const struct tcphdr *)data; + addr->src_port = tcp_hdr->th_sport; + addr->dst_port = tcp_hdr->th_dport; + break; + case LAYER_TYPE_UDP: + udp_hdr = (const struct udp_hdr *)data; + addr->src_port = udp_hdr->uh_sport; + addr->dst_port = udp_hdr->uh_dport; + break; + case LAYER_TYPE_IPV4: + ipv4_hdr = (const struct ip *)data; + addr->addr_type = ADDR_TUPLE4_TYPE_V4; + addr->addr_v4.src_addr = ipv4_hdr->ip_src; + addr->addr_v4.dst_addr = ipv4_hdr->ip_dst; + break; + case LAYER_TYPE_IPV6: + ipv6_hdr = (const struct ip6_hdr *)data; + addr->addr_type = ADDR_TUPLE4_TYPE_V6; + memcpy(&addr->addr_v6.src_addr, &ipv6_hdr->ip6_src, sizeof(addr->addr_v6.src_addr)); + memcpy(&addr->addr_v6.dst_addr, &ipv6_hdr->ip6_dst, sizeof(addr->addr_v6.dst_addr)); + break; + default: + break; + } +} + +static const char *layer_type2str(enum layer_type this_type) +{ + switch (this_type) + { + case LAYER_TYPE_ETHER: + return "ETHER"; + case LAYER_TYPE_PPP: + return "PPP"; + case LAYER_TYPE_HDLC: + return "HDLC"; + case LAYER_TYPE_VLAN: + return "VLAN"; + case LAYER_TYPE_PPPOE: + return "PPPOE"; + case LAYER_TYPE_MPLS: + return "MPLS"; + case LAYER_TYPE_IPV4: + return "IPV4"; + case LAYER_TYPE_IPV6: + return "IPV6"; + case LAYER_TYPE_UDP: + return "UDP"; + case LAYER_TYPE_TCP: + return "TCP"; + case LAYER_TYPE_G_VXLAN: + return "G_VXLAN"; + case LAYER_TYPE_GTPV1_U: + return "GTPV1_U"; + default: + return "UNKNOWN"; + } +} + +// FROM SAPP +static uint16_t parse_gtphdr_len(const struct gtp_hdr *gtph) +{ + const unsigned char *p_ext_hdr = (unsigned char *)gtph + sizeof(struct gtp_hdr); + unsigned char next_hdr_type; + unsigned char this_ext_field_cont_len; + + // v0 太古老已废弃,目前仅支持 GTPv1 版本 + if (((gtph->flags & GTP_HDR_VER_MASK) >> 5) != 1) + { + return -1; + } + + if (gtph->flags & (GTP_HDR_FLAG_SEQ_NUM | GTP_HDR_FLAG_N_PDU | GTP_HDR_FLAG_NEXT_EXT_HDR)) + { + // skip seq field (2 bytes) + p_ext_hdr += 2; + + // skip N-PDU field (1 byte) + p_ext_hdr++; + + // 解析 GTP 扩展头部字段,参考 wireshark 源码 packet-gtp.c->dissect_gtp_common() + next_hdr_type = *p_ext_hdr; + if (gtph->flags & GTP_HDR_FLAG_NEXT_EXT_HDR) + { + while (next_hdr_type != 0) + { + // 指向长度字段, 以4个字节为单位 + p_ext_hdr++; + this_ext_field_cont_len = *p_ext_hdr * 4 - 2; + + // 指向数据部分第一个字节 + p_ext_hdr++; + p_ext_hdr += this_ext_field_cont_len; + + // 指向下一个头部字段 + next_hdr_type = *p_ext_hdr; + p_ext_hdr++; + } + } + else + { + p_ext_hdr++; + } + } + + return (char *)p_ext_hdr - (char *)gtph; +} + +static const void *parse_ether(struct raw_pkt_parser *handler, const void *data, size_t length, enum layer_type this_type) +{ + if (length < sizeof(struct ethhdr)) + { + TFE_LOG_ERROR(g_default_logger, "%s: pkt_trace_id: %lu, this_layer: %s, err: data not enough", LOG_TAG_RAWPKT, handler->pkt_trace_id, layer_type2str(this_type)); + return data; + } + + if (raw_packet_parser_status(handler, data, this_type) == PARSE_STATUS_STOP) + { + return data; + } + + struct ethhdr *hdr = (struct ethhdr *)data; + uint16_t next_proto = ntohs(hdr->h_proto); + uint16_t hdr_len = sizeof(struct ethhdr); + const void *data_next_layer = (const char *)data + hdr_len; + size_t data_next_length = length - hdr_len; + + TFE_LOG_DEBUG(g_default_logger, "%s: pkt_trace_id: %lu, this_layer: %s, payload_len: [%lu/%lu]", LOG_TAG_RAWPKT, handler->pkt_trace_id, layer_type2str(this_type), data_next_length, length); + switch (next_proto) + { + case ETH_P_8021Q: + // TESTED + return parse_vlan8021q(handler, data_next_layer, data_next_length, LAYER_TYPE_VLAN); + case ETH_P_8021AD: + // TODO + return parse_ether(handler, data_next_layer, data_next_length, LAYER_TYPE_ETHER); + case ETH_P_IP: + // TESTED + return parse_ipv4(handler, data_next_layer, data_next_length, LAYER_TYPE_IPV4); + case ETH_P_IPV6: + // TESTED + return parse_ipv6(handler, data_next_layer, data_next_length, LAYER_TYPE_IPV6); + case ETH_P_PPP_SES: + // TODO + return parse_pppoe_ses(handler, data_next_layer, data_next_length, LAYER_TYPE_PPPOE); + case ETH_P_MPLS_UC: + // TESTED + return parse_mpls(handler, data_next_layer, data_next_length, LAYER_TYPE_MPLS); + default: + TFE_LOG_ERROR(g_default_logger, "%s: pkt_trace_id: %lu, this_layer: %s, stop parse next protocol %d", LOG_TAG_RAWPKT, handler->pkt_trace_id, layer_type2str(this_type), next_proto); + return data_next_layer; + } +} + +static const void *parse_ipv4(struct raw_pkt_parser *handler, const void *data, size_t length, enum layer_type this_type) +{ + if (length < sizeof(struct ip)) + { + TFE_LOG_ERROR(g_default_logger, "%s: pkt_trace_id: %lu, this_layer: %s, err: data not enough", LOG_TAG_RAWPKT, handler->pkt_trace_id, layer_type2str(this_type)); + return data; + } + + if (raw_packet_parser_status(handler, data, this_type) == PARSE_STATUS_STOP) + { + return data; + } + + struct ip *hdr = (struct ip *)data; + uint16_t next_proto = hdr->ip_p; + uint16_t hdr_len = (hdr->ip_hl & 0xf) * 4u; + const void *data_next_layer = (const char *)data + hdr_len; + size_t data_next_length = length - hdr_len; + + TFE_LOG_DEBUG(g_default_logger, "%s: pkt_trace_id: %lu, this_layer: %s, payload_len: [%lu/%lu]", LOG_TAG_RAWPKT, handler->pkt_trace_id, layer_type2str(this_type), data_next_length, length); + switch (next_proto) + { + case IPPROTO_TCP: + // TESTED + return parse_tcp(handler, data_next_layer, data_next_length, LAYER_TYPE_TCP); + case IPPROTO_UDP: + // TESTED + return parse_udp(handler, data_next_layer, data_next_length, LAYER_TYPE_UDP); + case IPPROTO_IPIP: + // TESTED + return parse_ipv4(handler, data_next_layer, data_next_length, LAYER_TYPE_IPV4); + case IPPROTO_IPV6: + // TESTED + return parse_ipv6(handler, data_next_layer, data_next_length, LAYER_TYPE_IPV6); + default: + TFE_LOG_ERROR(g_default_logger, "%s: pkt_trace_id: %lu, this_layer: %s, stop parse next protocol %d", LOG_TAG_RAWPKT, handler->pkt_trace_id, layer_type2str(this_type), next_proto); + return data_next_layer; + } +} + +static const void *parse_ipv6(struct raw_pkt_parser *handler, const void *data, size_t length, enum layer_type this_type) +{ + if (length < sizeof(struct ip6_hdr)) + { + TFE_LOG_ERROR(g_default_logger, "%s: pkt_trace_id: %lu, this_layer: %s, err: data not enough", LOG_TAG_RAWPKT, handler->pkt_trace_id, layer_type2str(this_type)); + return data; + } + + if (raw_packet_parser_status(handler, data, this_type) == PARSE_STATUS_STOP) + { + return data; + } + + struct ip6_hdr *hdr = (struct ip6_hdr *)data; + uint16_t next_proto = hdr->ip6_nxt; + uint16_t hdr_len = sizeof(struct ip6_hdr); + const void *data_next_layer = (const char *)data + hdr_len; + size_t data_next_length = length - hdr_len; + + TFE_LOG_DEBUG(g_default_logger, "%s: pkt_trace_id: %lu, this_layer: %s, payload_len: [%lu/%lu]", LOG_TAG_RAWPKT, handler->pkt_trace_id, layer_type2str(this_type), data_next_length, length); + switch (next_proto) + { + case IPPROTO_TCP: + // TESTED + return parse_tcp(handler, data_next_layer, data_next_length, LAYER_TYPE_TCP); + case IPPROTO_UDP: + // TESTED + return parse_udp(handler, data_next_layer, data_next_length, LAYER_TYPE_UDP); + case IPPROTO_IPIP: + // TESTED + return parse_ipv4(handler, data_next_layer, data_next_length, LAYER_TYPE_IPV4); + case IPPROTO_IPV6: + // TESTED + return parse_ipv6(handler, data_next_layer, data_next_length, LAYER_TYPE_IPV6); + default: + TFE_LOG_ERROR(g_default_logger, "%s: pkt_trace_id: %lu, this_layer: %s, stop parse next protocol %d", LOG_TAG_RAWPKT, handler->pkt_trace_id, layer_type2str(this_type), next_proto); + return data_next_layer; + } +} + +static const void *parse_tcp(struct raw_pkt_parser *handler, const void *data, size_t length, enum layer_type this_type) +{ + if (length < sizeof(struct tcphdr)) + { + TFE_LOG_ERROR(g_default_logger, "%s: pkt_trace_id: %lu, this_layer: %s, err: data not enough", LOG_TAG_RAWPKT, handler->pkt_trace_id, layer_type2str(this_type)); + return data; + } + + if (raw_packet_parser_status(handler, data, this_type) == PARSE_STATUS_STOP) + { + return data; + } + + struct tcphdr *hdr = (struct tcphdr *)data; + uint16_t hdr_len = hdr->th_off << 2; + const void *data_next_layer = (const char *)data + hdr_len; + size_t data_next_length = length - hdr_len; + + TFE_LOG_DEBUG(g_default_logger, "%s: pkt_trace_id: %lu, this_layer: %s, payload_len: [%lu/%lu]", LOG_TAG_RAWPKT, handler->pkt_trace_id, layer_type2str(this_type), data_next_length, length); + + return data_next_layer; +} + +static const void *parse_udp(struct raw_pkt_parser *handler, const void *data, size_t length, enum layer_type this_type) +{ + if (length < sizeof(struct udp_hdr)) + { + TFE_LOG_ERROR(g_default_logger, "%s: pkt_trace_id: %lu, this_layer: %s, err: data not enough", LOG_TAG_RAWPKT, handler->pkt_trace_id, layer_type2str(this_type)); + return data; + } + + if (raw_packet_parser_status(handler, data, this_type) == PARSE_STATUS_STOP) + { + return data; + } + + struct udp_hdr *hdr = (struct udp_hdr *)data; + uint16_t hdr_len = sizeof(struct udp_hdr); + const void *data_next_layer = (const char *)data + hdr_len; + size_t data_next_length = length - hdr_len; + + TFE_LOG_DEBUG(g_default_logger, "%s: pkt_trace_id: %lu, this_layer: %s, payload_len: [%lu/%lu]", LOG_TAG_RAWPKT, handler->pkt_trace_id, layer_type2str(this_type), data_next_length, length); + switch (ntohs(hdr->uh_dport)) + { + // VXLAN_DPORT + case 4789: + // TESTED + return parse_vxlan(handler, data_next_layer, data_next_length, LAYER_TYPE_G_VXLAN); + // GTP1U_PORT + case 2152: + // TESTED + return parse_gtpv1_u(handler, data_next_layer, data_next_length, LAYER_TYPE_GTPV1_U); + default: + return data_next_layer; + } +} + +static const void *parse_pppoe_ses(struct raw_pkt_parser *handler, const void *data, size_t length, enum layer_type this_type) +{ + if (length < 8) + { + TFE_LOG_ERROR(g_default_logger, "%s: pkt_trace_id: %lu, this_layer: %s, err: data not enough", LOG_TAG_RAWPKT, handler->pkt_trace_id, layer_type2str(this_type)); + return data; + } + + if (raw_packet_parser_status(handler, data, this_type) == PARSE_STATUS_STOP) + { + return data; + } + + uint16_t next_proto = *((uint16_t *)data + 3); + uint16_t hdr_len = 8; + const void *data_next_layer = (const char *)data + hdr_len; + size_t data_next_length = length - hdr_len; + + TFE_LOG_DEBUG(g_default_logger, "%s: pkt_trace_id: %lu, this_layer: %s, payload_len: [%lu/%lu]", LOG_TAG_RAWPKT, handler->pkt_trace_id, layer_type2str(this_type), data_next_length, length); + switch (next_proto) + { + // PPPOE_TYPE_IPV4 + case 0x2100: + // TESTED + return parse_ipv4(handler, data_next_layer, data_next_length, LAYER_TYPE_IPV4); + // PPPOE_TYPE_IPV6 + case 0x5700: + // TODO + return parse_ipv6(handler, data_next_layer, data_next_length, LAYER_TYPE_IPV6); + default: + TFE_LOG_ERROR(g_default_logger, "%s: pkt_trace_id: %lu, this_layer: %s, stop parse next protocol %d", LOG_TAG_RAWPKT, handler->pkt_trace_id, layer_type2str(this_type), next_proto); + return data_next_layer; + } +} + +static const void *parse_vxlan(struct raw_pkt_parser *handler, const void *data, size_t length, enum layer_type this_type) +{ + if (length < sizeof(struct vxlan_hdr)) + { + TFE_LOG_ERROR(g_default_logger, "%s: pkt_trace_id: %lu, this_layer: %s, err: data not enough", LOG_TAG_RAWPKT, handler->pkt_trace_id, layer_type2str(this_type)); + return NULL; + } + + if (raw_packet_parser_status(handler, data, this_type) == PARSE_STATUS_STOP) + { + return data; + } + + struct vxlan_hdr *vxlan_hdr = (struct vxlan_hdr *)data; + uint16_t hdr_len = sizeof(struct vxlan_hdr); + const void *data_next_layer = (const char *)data + hdr_len; + size_t data_next_length = length - hdr_len; + + TFE_LOG_DEBUG(g_default_logger, "%s: pkt_trace_id: %lu, this_layer: %s, payload_len: [%lu/%lu]", LOG_TAG_RAWPKT, handler->pkt_trace_id, layer_type2str(this_type), data_next_length, length); + // TESTED + return parse_ether(handler, data_next_layer, data_next_length, LAYER_TYPE_ETHER); +} + +static const void *parse_vlan8021q(struct raw_pkt_parser *handler, const void *data, size_t length, enum layer_type this_type) +{ + if (length < sizeof(struct vlan_hdr)) + { + TFE_LOG_ERROR(g_default_logger, "%s: pkt_trace_id: %lu, this_layer: %s, err: data not enough", LOG_TAG_RAWPKT, handler->pkt_trace_id, layer_type2str(this_type)); + return NULL; + } + + if (raw_packet_parser_status(handler, data, this_type) == PARSE_STATUS_STOP) + { + return data; + } + + struct vlan_hdr *hdr = (struct vlan_hdr *)data; + uint16_t next_proto = ntohs(hdr->protocol); + uint16_t hdr_len = sizeof(struct vlan_hdr); + const void *data_next_layer = (const char *)data + hdr_len; + size_t data_next_length = length - hdr_len; + + TFE_LOG_DEBUG(g_default_logger, "%s: pkt_trace_id: %lu, this_layer: %s, payload_len: [%lu/%lu]", LOG_TAG_RAWPKT, handler->pkt_trace_id, layer_type2str(this_type), data_next_length, length); + switch (next_proto) + { + case ETH_P_8021Q: + // TESTED + return parse_vlan8021q(handler, data_next_layer, data_next_length, LAYER_TYPE_VLAN); + case ETH_P_IP: + // TESTED + return parse_ipv4(handler, data_next_layer, data_next_length, LAYER_TYPE_IPV4); + case ETH_P_IPV6: + // TODO + return parse_ipv6(handler, data_next_layer, data_next_length, LAYER_TYPE_IPV6); + case ETH_P_PPP_SES: + // TESTED + return parse_pppoe_ses(handler, data_next_layer, data_next_length, LAYER_TYPE_PPPOE); + case ETH_P_MPLS_UC: + // TODO + return parse_mpls(handler, data_next_layer, data_next_length, LAYER_TYPE_MPLS); + default: + TFE_LOG_ERROR(g_default_logger, "%s: pkt_trace_id: %lu, this_layer: %s, stop parse next protocol %d", LOG_TAG_RAWPKT, handler->pkt_trace_id, layer_type2str(this_type), next_proto); + return data_next_layer; + } +} + +static const void *parse_gtpv1_u(struct raw_pkt_parser *handler, const void *data, size_t length, enum layer_type this_type) +{ + if (length < sizeof(struct gtp_hdr)) + { + TFE_LOG_ERROR(g_default_logger, "%s: pkt_trace_id: %lu, this_layer: %s, err: data not enough", LOG_TAG_RAWPKT, handler->pkt_trace_id, layer_type2str(this_type)); + return NULL; + } + + uint16_t hdr_len = parse_gtphdr_len((const struct gtp_hdr *)data); + if (hdr_len < 0) + { + return data; + } + + if (raw_packet_parser_status(handler, data, this_type) == PARSE_STATUS_STOP) + { + return data; + } + + uint8_t next_proto = (((const uint8_t *)((const char *)data + hdr_len))[0]) >> 4; + const void *data_next_layer = (const char *)data + hdr_len; + size_t data_next_length = length - hdr_len; + + TFE_LOG_DEBUG(g_default_logger, "%s: pkt_trace_id: %lu, this_layer: %s, payload_len: [%lu/%lu]", LOG_TAG_RAWPKT, handler->pkt_trace_id, layer_type2str(this_type), data_next_length, length); + switch (next_proto) + { + case 4: + // TESTED + return parse_ipv4(handler, data_next_layer, data_next_length, LAYER_TYPE_IPV4); + case 6: + // TESTED + return parse_ipv6(handler, data_next_layer, data_next_length, LAYER_TYPE_IPV6); + default: + TFE_LOG_ERROR(g_default_logger, "%s: pkt_trace_id: %lu, this_layer: %s, stop parse next protocol %d", LOG_TAG_RAWPKT, handler->pkt_trace_id, layer_type2str(this_type), next_proto); + return data_next_layer; + } +} + +static const void *parse_mpls(struct raw_pkt_parser *handler, const void *data, size_t length, enum layer_type this_type) +{ + if (length < 4) + { + TFE_LOG_ERROR(g_default_logger, "%s: pkt_trace_id: %lu, this_layer: %s, err: data not enough", LOG_TAG_RAWPKT, handler->pkt_trace_id, layer_type2str(this_type)); + return data; + } + + if (raw_packet_parser_status(handler, data, this_type) == PARSE_STATUS_STOP) + { + return data; + } + +#define MPLS_LABEL_MASK (0xFFFFF000) +#define MPLS_EXP_MASK (0x00000E00) +#define MPLS_BLS_MASK (0x00000100) +#define MPLS_TTL_MASK (0x000000FF) + + /* + * MPLS Format + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Label | Exp |S| TTL | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * Label : Label Value 20 bits + * Exp : Experimental Use 3 bits + * S : Bottom of Stack 1 bit + * TTL : Time to Live 8 bits + */ + + uint32_t *hdr = (uint32_t *)data; + // unsigned int mpls_label = (ntohl(*hdr) & MPLS_LABEL_MASK) >> 12; + // unsigned int mpls_exp = (ntohl(*hdr) & MPLS_EXP_MASK) >> 9; + unsigned int mpls_bls = (ntohl(*hdr) & MPLS_BLS_MASK) >> 8; + // unsigned int mpls_ttl = (ntohl(*hdr) & MPLS_TTL_MASK); + + uint16_t hdr_len = 4; + const void *data_next_layer = (const char *)data + hdr_len; + size_t data_next_length = length - hdr_len; + + if (mpls_bls == 1) + { + uint8_t ip_version = (((uint8_t *)data_next_layer)[0]) >> 4; + if (ip_version == 0) + { + /* + * PW Ethernet Control Word + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |0 0 0 0| Reserved | Sequence Number | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * Reference: https://tools.ietf.org/html/rfc4448 + */ + data_next_layer = (const char *)data_next_layer + 4; + data_next_length = data_next_length - 4; + + TFE_LOG_DEBUG(g_default_logger, "%s: pkt_trace_id: %lu, this_layer: %s, payload_len: [%lu/%lu]", LOG_TAG_RAWPKT, handler->pkt_trace_id, layer_type2str(this_type), data_next_length, length); + // TESTED + return parse_ether(handler, data_next_layer, data_next_length, LAYER_TYPE_ETHER); + } + else if (ip_version == 4) + { + TFE_LOG_DEBUG(g_default_logger, "%s: pkt_trace_id: %lu, this_layer: %s, payload_len: [%lu/%lu]", LOG_TAG_RAWPKT, handler->pkt_trace_id, layer_type2str(this_type), data_next_length, length); + // TESTED + return parse_ipv4(handler, data_next_layer, data_next_length, LAYER_TYPE_IPV4); + } + else if (ip_version == 6) + { + TFE_LOG_DEBUG(g_default_logger, "%s: pkt_trace_id: %lu, this_layer: %s, payload_len: [%lu/%lu]", LOG_TAG_RAWPKT, handler->pkt_trace_id, layer_type2str(this_type), data_next_length, length); + // TODO + return parse_ipv6(handler, data_next_layer, data_next_length, LAYER_TYPE_IPV6); + } + else + { + TFE_LOG_DEBUG(g_default_logger, "%s: pkt_trace_id: %lu, this_layer: %s, payload_len: [%lu/%lu]", LOG_TAG_RAWPKT, handler->pkt_trace_id, layer_type2str(this_type), data_next_length, length); + // TODO + return parse_ether(handler, data_next_layer, data_next_length, LAYER_TYPE_ETHER); + } + } + else + { + TFE_LOG_DEBUG(g_default_logger, "%s: pkt_trace_id: %lu, this_layer: %s, payload_len: [%lu/%lu]", LOG_TAG_RAWPKT, handler->pkt_trace_id, layer_type2str(this_type), data_next_length, length); + // TESTED + return parse_mpls(handler, data_next_layer, data_next_length, LAYER_TYPE_MPLS); + } +} \ No newline at end of file diff --git a/common/src/tfe_session_table.cpp b/common/src/tfe_session_table.cpp new file mode 100644 index 0000000..985a149 --- /dev/null +++ b/common/src/tfe_session_table.cpp @@ -0,0 +1,219 @@ +#include + +#include "tfe_session_table.h" +#include + +struct session_table +{ + struct session_node *root_by_id; + struct session_node *root_by_addr; + uint64_t session_node_count; +}; + +// Note: session_addr must be initialized by memset(0) before use !!! + +struct session_table *session_table_create() +{ + struct session_table *table = (struct session_table *)calloc(1, sizeof(struct session_table)); + assert(table); + table->session_node_count = 0; + + return table; +} + +void session_table_destory(struct session_table *table) +{ + if (table) + { + struct session_node *temp = NULL; + struct session_node *node = NULL; + HASH_ITER(hh1, table->root_by_id, node, temp) + { + HASH_DELETE(hh1, table->root_by_id, node); + HASH_DELETE(hh2, table->root_by_addr, node); + + if (node->val_freecb && node->val_data) + { + node->val_freecb(node->val_data); + } + + free(node); + node = NULL; + } + + free(table); + table = NULL; + } +} + +void session_table_reset(struct session_table *table) +{ + if (table) + { + struct session_node *temp = NULL; + struct session_node *node = NULL; + HASH_ITER(hh1, table->root_by_id, node, temp) + { + HASH_DELETE(hh1, table->root_by_id, node); + HASH_DELETE(hh2, table->root_by_addr, node); + + if (node->val_freecb && node->val_data) + { + node->val_freecb(node->val_data); + } + + free(node); + node = NULL; + + table->session_node_count--; + } + } +} + +uint64_t session_table_count(struct session_table *table) +{ + if (table) + { + return table->session_node_count; + } + else + { + return 0; + } +} + +// session_addr : deep copy +// val_data : shallow copy (malloc by user, free by val_freecb) +int session_table_insert(struct session_table *table, uint64_t session_id, const struct addr_tuple4 *session_addr, void *val_data, const fn_free_cb *val_freecb) +{ + struct session_node *temp = NULL; + HASH_FIND(hh1, table->root_by_id, &session_id, sizeof(session_id), temp); + if (temp) + { + TFE_LOG_DEBUG(g_default_logger, "%s: insert: key %lu exists", LOG_TAG_STABLE, session_id); + return -1; + } + + temp = (struct session_node *)calloc(1, sizeof(struct session_node)); + assert(temp); + + temp->session_id = session_id; + memcpy(&temp->session_addr, session_addr, sizeof(struct addr_tuple4)); + temp->val_data = val_data; + temp->val_freecb = val_freecb; + + HASH_ADD(hh1, table->root_by_id, session_id, sizeof(temp->session_id), temp); + HASH_ADD(hh2, table->root_by_addr, session_addr, sizeof(temp->session_addr), temp); + + TFE_LOG_DEBUG(g_default_logger, "%s: insert: key %lu success", LOG_TAG_STABLE, session_id); + table->session_node_count++; + + return 0; +} + +int session_table_delete_by_id(struct session_table *table, uint64_t session_id) +{ + struct session_node *temp = NULL; + HASH_FIND(hh1, table->root_by_id, &session_id, sizeof(session_id), temp); + if (!temp) + { + TFE_LOG_DEBUG(g_default_logger, "%s: delete: key %lu not exists", LOG_TAG_STABLE, session_id); + return -1; + } + + HASH_DELETE(hh1, table->root_by_id, temp); + HASH_DELETE(hh2, table->root_by_addr, temp); + + if (temp->val_freecb && temp->val_data) + { + temp->val_freecb(temp->val_data); + temp->val_data = NULL; + } + + free(temp); + temp = NULL; + + TFE_LOG_DEBUG(g_default_logger, "%s: delete: key %lu success", LOG_TAG_STABLE, session_id); + table->session_node_count--; + + return 0; +} + +int session_table_delete_by_addr(struct session_table *table, const struct addr_tuple4 *session_addr) +{ + struct session_node *temp = NULL; + char *addr_str = addr_tuple4_to_str(session_addr); + HASH_FIND(hh2, table->root_by_addr, session_addr, sizeof(struct addr_tuple4), temp); + if (!temp) + { + struct addr_tuple4 reverse_addr; + addr_tuple4_reverse(session_addr, &reverse_addr); + HASH_FIND(hh2, table->root_by_addr, &reverse_addr, sizeof(struct addr_tuple4), temp); + if (!temp) + { + TFE_LOG_DEBUG(g_default_logger, "%s: delete: key %s not exists", LOG_TAG_STABLE, addr_str); + free(addr_str); + return -1; + } + } + + HASH_DELETE(hh1, table->root_by_id, temp); + HASH_DELETE(hh2, table->root_by_addr, temp); + + if (temp->val_freecb && temp->val_data) + { + temp->val_freecb(temp->val_data); + temp->val_data = NULL; + } + + free(temp); + temp = NULL; + + TFE_LOG_DEBUG(g_default_logger, "%s: delete: key %s success", LOG_TAG_STABLE, addr_str); + free(addr_str); + addr_str = NULL; + table->session_node_count--; + + return 0; +} + +struct session_node *session_table_search_by_id(struct session_table *table, uint64_t session_id) +{ + struct session_node *temp = NULL; + HASH_FIND(hh1, table->root_by_id, &session_id, sizeof(session_id), temp); + if (!temp) + { + TFE_LOG_DEBUG(g_default_logger, "%s: search: key %lu not exists", LOG_TAG_STABLE, session_id); + return NULL; + } + + TFE_LOG_DEBUG(g_default_logger, "%s: search: key %lu success", LOG_TAG_STABLE, session_id); + + return temp; +} + +struct session_node *session_table_search_by_addr(struct session_table *table, const struct addr_tuple4 *session_addr) +{ + struct session_node *temp = NULL; + char *addr_str = addr_tuple4_to_str(session_addr); + HASH_FIND(hh2, table->root_by_addr, session_addr, sizeof(struct addr_tuple4), temp); + if (!temp) + { + struct addr_tuple4 reverse_addr; + addr_tuple4_reverse(session_addr, &reverse_addr); + HASH_FIND(hh2, table->root_by_addr, &reverse_addr, sizeof(struct addr_tuple4), temp); + if (!temp) + { + TFE_LOG_DEBUG(g_default_logger, "%s: search: key %s not exists", LOG_TAG_STABLE, addr_str); + free(addr_str); + addr_str = NULL; + return NULL; + } + } + + TFE_LOG_DEBUG(g_default_logger, "%s: search: key %s success", LOG_TAG_STABLE, addr_str); + free(addr_str); + addr_str = NULL; + + return temp; +} diff --git a/common/src/tfe_tap_rss.cpp b/common/src/tfe_tap_rss.cpp new file mode 100644 index 0000000..61286e8 --- /dev/null +++ b/common/src/tfe_tap_rss.cpp @@ -0,0 +1,389 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#if (SUPPORT_BPF) +#include "../../bpf/bpf_conf_user.h" +#include +#include +#endif + +#include "tfe_acceptor_kni.h" +#include "tfe_tap_rss.h" +#include "tfe_utils.h" + +#ifndef TUN_PATH +#define TUN_PATH "/dev/net/tun" +#endif + +struct bpf_ctx +{ + int bpf_prog_fd; + int bpf_map_fd; + + char bpf_file[1024]; +#if (SUPPORT_BPF) + struct bpf_object *bpf_obj; + bpf_conf_t bpf_conf; +#endif +}; + +int tfe_tap_get_bpf_prog_fd(struct bpf_ctx *ctx) +{ + if (ctx) + { + return ctx->bpf_prog_fd; + } + else + { + return -1; + } +} + +#if (SUPPORT_BPF) +void tfe_tap_global_unload_rss_bpf(struct bpf_ctx *ctx) +{ + if (ctx) + { + if (ctx->bpf_prog_fd > 0) + { + close(ctx->bpf_prog_fd); + } + + if (ctx->bpf_obj) + { + bpf_object__close(ctx->bpf_obj); + ctx->bpf_obj = NULL; + } + + free(ctx); + ctx = NULL; + } +} +#else +void tfe_tap_global_unload_rss_bpf(struct bpf_ctx *ctx) +{ +} +#endif + +/* + * bpf_queue_num : worker thread number + * bpf_default_queue : -1: for disable(only use for debug, rss to one queue) + * bpf_hash_mode : 2: hash with src/dst addr + * 4: hash with src/dst addr and src/dst port + * bpf_debug_log : 0 for disable(only use for debug, printf bpf debug log) + */ +#if (SUPPORT_BPF) +struct bpf_ctx *tfe_tap_global_load_rss_bpf(const char *bpf_obj_file, uint32_t bpf_queue_num, uint32_t bpf_hash_mode, uint32_t bpf_debug_log, void *logger) +{ + struct bpf_ctx *ctx = (struct bpf_ctx *)calloc(1, sizeof(struct bpf_ctx)); + strncpy(ctx->bpf_file, bpf_obj_file, strlen(bpf_obj_file)); + + bpf_conf_set_debug_log(&ctx->bpf_conf, bpf_debug_log); + bpf_conf_set_hash_mode(&ctx->bpf_conf, bpf_hash_mode); + bpf_conf_set_queue_num(&ctx->bpf_conf, bpf_queue_num); + + if (bpf_prog_load(ctx->bpf_file, BPF_PROG_TYPE_SOCKET_FILTER, &ctx->bpf_obj, &ctx->bpf_prog_fd) < 0) + { + TFE_LOG_ERROR(g_default_logger, TAP_RSS_LOG_TAG "unable to load bpf object %s, aborting: %s", ctx->bpf_file, strerror(errno)); + goto error; + } + + if (bpf_conf_update_map(&ctx->bpf_conf, ctx->bpf_obj) == -1) + { + goto error; + } + + return ctx; + +error: + tfe_tap_global_unload_rss_bpf(ctx); + + return NULL; +} +#else +struct bpf_ctx *tfe_tap_global_load_rss_bpf(const char *bpf_obj_file, uint32_t bpf_queue_num, uint32_t bpf_hash_mode, uint32_t bpf_debug_log, void *logger) +{ + TFE_LOG_ERROR(g_default_logger, TAP_RSS_LOG_TAG "not support bpf"); + return NULL; +} +#endif + +struct tap_ctx *tfe_tap_ctx_create(void *ctx) +{ + struct acceptor_thread_ctx *thread_ctx = (struct acceptor_thread_ctx *)ctx; + struct acceptor_ctx *acceptor_ctx = thread_ctx->ref_acceptor_ctx; + struct tap_ctx *tap_ctx = (struct tap_ctx *)calloc(1, sizeof(struct tap_ctx)); + assert(tap_ctx != NULL); + + tap_ctx->tap_fd = tfe_tap_open_per_thread(acceptor_ctx->config->tap_device, IFF_TAP | IFF_NO_PI | IFF_MULTI_QUEUE, tfe_tap_get_bpf_prog_fd(acceptor_ctx->config->tap_bpf_ctx), g_default_logger); + tap_ctx->tap_c = tfe_tap_open_per_thread(acceptor_ctx->config->tap_c_device, IFF_TAP | IFF_NO_PI | IFF_MULTI_QUEUE, tfe_tap_get_bpf_prog_fd(acceptor_ctx->config->tap_bpf_ctx), g_default_logger); + tap_ctx->tap_s = tfe_tap_open_per_thread(acceptor_ctx->config->tap_s_device, IFF_TAP | IFF_NO_PI | IFF_MULTI_QUEUE, tfe_tap_get_bpf_prog_fd(acceptor_ctx->config->tap_bpf_ctx), g_default_logger); + + return tap_ctx; +} + +struct tap_config *tfe_tap_config_create(const char *profile, int thread_num) +{ + int ret = 0; + int tap_allow_mutilthread = 0; + uint32_t bpf_debug_log = 0; + uint32_t bpf_hash_mode = 2; + uint32_t bpf_queue_num = thread_num; + char bpf_obj[1024] = {0}; + + struct tap_config *tap = (struct tap_config *)calloc(1, sizeof(struct tap_config)); + assert(tap != NULL); + + MESA_load_profile_int_nodef(profile, "tap", "tap_rps_enable", &tap->tap_rps_enable); + MESA_load_profile_string_def(profile, "tap", "tap_name", tap->tap_device, sizeof(tap->tap_device), "tap0"); + MESA_load_profile_string_def(profile, "traffic_steering", "device_client", tap->tap_c_device, sizeof(tap->tap_c_device), "tap_c"); + MESA_load_profile_string_def(profile, "traffic_steering", "device_server", tap->tap_s_device, sizeof(tap->tap_s_device), "tap_s"); + MESA_load_profile_int_nodef(profile, "tap", "bpf_debug_log", (int *)&bpf_debug_log); + MESA_load_profile_int_nodef(profile, "tap", "bpf_hash_mode", (int *)&bpf_hash_mode); + MESA_load_profile_string_nodef(profile, "tap", "bpf_obj", bpf_obj, sizeof(bpf_obj)); + MESA_load_profile_int_nodef(profile, "tap", "tap_allow_mutilthread", &tap_allow_mutilthread); + + MESA_load_profile_int_nodef(profile, "io_uring", "enable_iouring", &tap->enable_iouring); + MESA_load_profile_int_nodef(profile, "io_uring", "enable_debuglog", &tap->enable_debuglog); + MESA_load_profile_int_nodef(profile, "io_uring", "ring_size", &tap->ring_size); + MESA_load_profile_int_nodef(profile, "io_uring", "buff_size", &tap->buff_size); + MESA_load_profile_int_nodef(profile, "io_uring", "flags", &tap->flags); + MESA_load_profile_int_nodef(profile, "io_uring", "sq_thread_idle", &tap->sq_thread_idle); + + char src_mac_addr_str[TFE_SYMBOL_MAX]; + ret = MESA_load_profile_string_nodef(profile, "system", "src_mac_addr", src_mac_addr_str, sizeof(src_mac_addr_str)); + if(ret < 0){ + TFE_LOG_ERROR(g_default_logger, "MESA_prof_load: src_mac_addr not set, profile = %s, section = system", profile); + goto error_out; + } + str_to_mac(src_mac_addr_str, tap->src_mac); + get_mac_by_device_name(tap->tap_device, tap->tap_mac); + get_mac_by_device_name(tap->tap_c_device, tap->tap_c_mac); + get_mac_by_device_name(tap->tap_s_device, tap->tap_s_mac); + + if (tap->tap_rps_enable) + { + if (MESA_load_profile_string_nodef(profile, "tap", "tap_rps_mask", tap->tap_rps_mask, sizeof(tap->tap_rps_mask)) < 0) + { + TFE_LOG_ERROR(g_default_logger, TAP_RSS_LOG_TAG "under tap mode, when enable tap_rps_enable, tap_rps_mask is required."); + goto error_out; + } + } + + if (tap_allow_mutilthread) + { + tap->tap_bpf_ctx = tfe_tap_global_load_rss_bpf(bpf_obj, bpf_queue_num, bpf_hash_mode, bpf_debug_log, g_default_logger); + if (tap->tap_bpf_ctx == NULL) + { + goto error_out; + } + } + else if (thread_num > 1){ + TFE_LOG_ERROR(g_default_logger, TAP_RSS_LOG_TAG "under tap mode, when disable tap_allow_mutilthread, only support one work thread."); + goto error_out; + } + + return tap; + +error_out: + tfe_tap_destory(tap); + return NULL; +} + +void tfe_tap_destory(struct tap_config *tap) +{ + if (tap) + { + if (tap->tap_bpf_ctx) + { + tfe_tap_global_unload_rss_bpf(tap->tap_bpf_ctx); + tap->tap_bpf_ctx = NULL; + } + + free(tap); + tap = NULL; + } +} + +int tfe_tap_set_rps(void *local_logger, const char *tap_name, int thread_num, const char *rps_mask) +{ + char file[1024] = {0}; + + memset(file, 0, sizeof(file)); + snprintf(file, sizeof(file), "/sys/class/net/%s/queues/rx-%d/rps_cpus", tap_name, thread_num); + + FILE *fp = fopen(file, "w"); + if (fp == NULL) + { + TFE_LOG_ERROR(local_logger, "%s can't open %s, %s", TAP_RSS_LOG_TAG, file, strerror(errno)); + return -1; + } + + fwrite(rps_mask, strlen(rps_mask), 1, fp); + TFE_LOG_DEBUG(local_logger, TAP_RSS_LOG_TAG "set rps '%s' to %s", rps_mask, file); + fclose(fp); + return 0; +} + +int tfe_tap_open_per_thread(const char *tap_dev, int tap_flags, int bpf_prog_fd, void *logger) +{ + int fd = -1; + int tap_fd = -1; + int nonblock_flags = -1; + struct ifreq ifr; + + tap_fd = open(TUN_PATH, O_RDWR); + if (tap_fd == -1) + { + TFE_LOG_ERROR(g_default_logger, TAP_RSS_LOG_TAG "unable to open " TUN_PATH ", aborting: %s", strerror(errno)); + return -1; + } + + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_flags = tap_flags; + strcpy(ifr.ifr_name, tap_dev); + if (ioctl(tap_fd, TUNSETIFF, &ifr) == -1) + { + TFE_LOG_ERROR(g_default_logger, TAP_RSS_LOG_TAG "unable to attach %s, aborting: %s", tap_dev, strerror(errno)); + goto error; + } + + /* + * The TUNSETPERSIST ioctl can be used to make the TUN/TAP interface persistent. + * In this mode, the interface won't be destroyed when the last process closes the associated /dev/net/tun file descriptor. + */ + /* + if (ioctl(tap_fd, TUNSETPERSIST, 1) == -1) + { + TFE_LOG_ERROR(g_default_logger, TAP_RSS_LOG_TAG "unable to set persist on %s, aborting: %s", tap_dev, strerror(errno)); + goto error; + } + */ +#if (SUPPORT_BPF) + if (bpf_prog_fd > 0) + { + // Set bpf + if (ioctl(tap_fd, TUNSETSTEERINGEBPF, (void *)&bpf_prog_fd) == -1) + { + TFE_LOG_ERROR(g_default_logger, TAP_RSS_LOG_TAG "unable to set bpf on %s, aborting: %s", tap_dev, strerror(errno)); + goto error; + } + } +#endif + + // Set nonblock + nonblock_flags = fcntl(tap_fd, F_GETFL); + if (nonblock_flags == -1) + { + TFE_LOG_ERROR(g_default_logger, TAP_RSS_LOG_TAG "unable to get nonblock flags on %s fd, aborting: %s", tap_dev, strerror(errno)); + goto error; + } + nonblock_flags |= O_NONBLOCK; + if (fcntl(tap_fd, F_SETFL, nonblock_flags) == -1) + { + TFE_LOG_ERROR(g_default_logger, TAP_RSS_LOG_TAG "unable to set nonblock flags on %s fd, aborting: %s", tap_dev, strerror(errno)); + goto error; + } + + // Get MTU + fd = socket(PF_INET, SOCK_DGRAM, 0); + if (fd == -1) + { + TFE_LOG_ERROR(g_default_logger, TAP_RSS_LOG_TAG "unable to create socket, aborting: %s", strerror(errno)); + goto error; + } + + memset(&ifr, 0, sizeof(ifr)); + strcpy(ifr.ifr_name, tap_dev); + if (ioctl(fd, SIOCGIFMTU, &ifr) < 0) + { + TFE_LOG_ERROR(g_default_logger, TAP_RSS_LOG_TAG "unable to get MTU on %s, aborting: %s", tap_dev, strerror(errno)); + goto error; + } + + // Set eth up + if (ioctl(fd, SIOCGIFFLAGS, &ifr) == -1) + { + TFE_LOG_ERROR(g_default_logger, TAP_RSS_LOG_TAG "unable to get link status on %s, aborting: %s", tap_dev, strerror(errno)); + goto error; + } + + if ((ifr.ifr_flags & IFF_UP) == 0) + { + ifr.ifr_flags |= IFF_UP; + if (ioctl(fd, SIOCSIFFLAGS, &ifr) < 0) + { + TFE_LOG_ERROR(g_default_logger, TAP_RSS_LOG_TAG "unable to set link status on %s, aborting: %s", tap_dev, strerror(errno)); + goto error; + } + } + + TFE_LOG_INFO(logger, TAP_RSS_LOG_TAG "using tap device %s with MTU %d", tap_dev, ifr.ifr_mtu); + close(fd); + + return tap_fd; + +error: + + if (fd > 0) + { + close(fd); + fd = -1; + } + + if (tap_fd > 0) + { + close(tap_fd); + tap_fd = -1; + } + + return -1; +} + +void tfe_tap_close_per_thread(int tap_fd) +{ + if (tap_fd > 0) + { + close(tap_fd); + } +} + +int tfe_tap_read_per_thread(int tap_fd, char *buff, int buff_size, void *logger) +{ + int ret = read(tap_fd, buff, buff_size); + if (ret < 0) + { + if (errno != EWOULDBLOCK && errno != EAGAIN) + { + TFE_LOG_ERROR(g_default_logger, TAP_RSS_LOG_TAG "unable to read data from tapfd %d, aborting: %s", tap_fd, strerror(errno)); + } + } + + return ret; +} + +int tfe_tap_write_per_thread(int tap_fd, const char *data, int data_len, void *logger) +{ + int ret = write(tap_fd, data, data_len); + if (ret != data_len) + { + TFE_LOG_ERROR(g_default_logger, TAP_RSS_LOG_TAG "need send %dB, only send %dB, aborting: %s", data_len, ret, strerror(errno)); + } + + return ret; +} \ No newline at end of file diff --git a/common/src/tfe_timestamp.cpp b/common/src/tfe_timestamp.cpp new file mode 100644 index 0000000..4e8949d --- /dev/null +++ b/common/src/tfe_timestamp.cpp @@ -0,0 +1,65 @@ +#include +#include + +#include +#include "tfe_timestamp.h" + +// 1 s = 1000 ms +// 1 ms = 1000 us +// 1 us = 1000 ns + +struct timestamp +{ + struct timespec timestamp; + uint64_t update_interval_ms; +}; + +struct timestamp *timestamp_new(uint64_t update_interval_ms) +{ + struct timestamp *ts = (struct timestamp *)calloc(1, sizeof(struct timestamp)); + ts->update_interval_ms = update_interval_ms; + + timestamp_update(ts); + TFE_LOG_DEBUG(g_default_logger, "%s: TIMESTAMP->update_interval_ms : %lu", LOG_TAG_TIMESTAMP, timestamp_update_interval_ms(ts)); + TFE_LOG_DEBUG(g_default_logger, "%s: TIMESTAMP->current_sec : %lu", LOG_TAG_TIMESTAMP, timestamp_get_sec(ts)); + TFE_LOG_DEBUG(g_default_logger, "%s: TIMESTAMP->current_msec : %lu", LOG_TAG_TIMESTAMP, timestamp_get_msec(ts)); + + return ts; +} + +void timestamp_free(struct timestamp *ts) +{ + if (ts) + { + free(ts); + ts = NULL; + } +} + +void timestamp_update(struct timestamp *ts) +{ + struct timespec temp; + clock_gettime(CLOCK_MONOTONIC, &temp); + ATOMIC_SET(&(ts->timestamp.tv_sec), temp.tv_sec); + ATOMIC_SET(&(ts->timestamp.tv_nsec), temp.tv_nsec); +} + +uint64_t timestamp_update_interval_ms(struct timestamp *ts) +{ + return ts->update_interval_ms; +} + +uint64_t timestamp_get_sec(struct timestamp *ts) +{ + uint64_t sec = ATOMIC_READ(&(ts->timestamp.tv_sec)); + + return sec; +} + +uint64_t timestamp_get_msec(struct timestamp *ts) +{ + uint64_t sec = ATOMIC_READ(&(ts->timestamp.tv_sec)); + uint64_t nsec = ATOMIC_READ(&(ts->timestamp.tv_nsec)); + + return sec * 1000 + nsec / 1000000; +} diff --git a/common/src/tfe_utils.cpp b/common/src/tfe_utils.cpp index 5f66d30..4e8ef84 100644 --- a/common/src/tfe_utils.cpp +++ b/common/src/tfe_utils.cpp @@ -6,6 +6,12 @@ #include #include #include +#include +#include +#include +#include +#include +#include //functioned as strdup, for dictator compatible. char* tfe_strdup(const char* s) @@ -215,4 +221,173 @@ int tfe_decode_base64url(u_char *dst, u_char *src) }; return tfe_decode_base64_internal(dst, src, basis64); -} \ No newline at end of file +} + +/****************************************************************************** + * sids + ******************************************************************************/ + +void sids_write_once(struct sids *dst, struct sids *src) +{ + if (dst && src) + { + if (dst->num == 0 && src->num > 0) + { + sids_copy(dst, src); + } + } +} + +void sids_copy(struct sids *dst, struct sids *src) +{ + if (dst && src) + { + dst->num = src->num; + memcpy(dst->elems, src->elems, sizeof(dst->elems[0]) * dst->num); + } +} + +/****************************************************************************** + * route_ctx + ******************************************************************************/ + +int route_ctx_is_empty(struct route_ctx *ctx) +{ + if (ctx->len == 0) + { + return 1; + } + else + { + return 0; + } +} + +void route_ctx_copy(struct route_ctx *dst, struct route_ctx *src) +{ + memcpy(dst->data, src->data, src->len); + dst->len = src->len; +} + + +/****************************************************************************** + * protocol + ******************************************************************************/ + +#define CHECKSUM_CARRY(x) (x = (x >> 16) + (x & 0xffff), (~(x + (x >> 16)) & 0xffff)) + +static int checksum(u_int16_t *addr, int len) +{ + int sum = 0; + int nleft = len; + u_int16_t ans = 0; + u_int16_t *w = addr; + + while (nleft > 1) + { + sum += *w++; + nleft -= 2; + } + + if (nleft == 1) + { + *(char *)(&ans) = *(char *)w; + sum += ans; + } + + return sum; +} + +void build_udp_header(const char *l3_hdr, int l3_hdr_len, struct udp_hdr *udp_hdr, u_int16_t udp_sport, u_int16_t udp_dport, int payload_len) +{ + memset(udp_hdr, 0, sizeof(struct udp_hdr)); + + int udp_hlen = sizeof(struct udp_hdr) + payload_len; + + udp_hdr->uh_sport = htons(udp_sport); + udp_hdr->uh_dport = htons(udp_dport); + + udp_hdr->uh_ulen = htons(udp_hlen); + udp_hdr->uh_sum = 0; + + int sum = checksum((u_int16_t *)l3_hdr, l3_hdr_len); + sum += ntohs(IPPROTO_UDP + udp_hlen); + sum += checksum((u_int16_t *)udp_hdr, udp_hlen); + udp_hdr->uh_sum = CHECKSUM_CARRY(sum); +} + +void build_ip_header(struct ip *ip_hdr, u_int8_t next_protocol, const char *src_addr, const char *dst_addr, uint16_t payload_len) +{ + memset(ip_hdr, 0, sizeof(struct ip)); + + ip_hdr->ip_hl = 5; /* 20 byte header */ + ip_hdr->ip_v = 4; /* version 4 */ + ip_hdr->ip_tos = 0; /* IP tos */ + ip_hdr->ip_id = htons(random()); /* IP ID */ + ip_hdr->ip_ttl = 80; /* time to live */ + ip_hdr->ip_p = next_protocol; /* transport protocol */ + ip_hdr->ip_src.s_addr = inet_addr(src_addr); + ip_hdr->ip_dst.s_addr = inet_addr(dst_addr); + ip_hdr->ip_len = htons(sizeof(struct ip) + payload_len); /* total length */ + ip_hdr->ip_off = htons(0); /* fragmentation flags */ + ip_hdr->ip_sum = 0; /* do this later */ + + int sum = checksum((u_int16_t *)ip_hdr, 20); + ip_hdr->ip_sum = CHECKSUM_CARRY(sum); +} + +// l3_protocol: ETH_P_IPV6/ETH_P_IP +void build_ether_header(struct ethhdr *eth_hdr, uint16_t next_protocol, const char *src_mac, const char *dst_mac) +{ + memset(eth_hdr, 0, sizeof(struct ethhdr)); + + str_to_mac(src_mac, (char *)eth_hdr->h_source); + str_to_mac(dst_mac, (char *)eth_hdr->h_dest); + eth_hdr->h_proto = htons(next_protocol); +} + +int str_to_mac(const char *str, char *mac_buff) +{ + if (sscanf(str, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", &(mac_buff[0]), &(mac_buff[1]), &(mac_buff[2]), &(mac_buff[3]), &(mac_buff[4]), &(mac_buff[5])) == 6) + { + return 0; + } + else + { + return -1; + } +} + +int get_mac_by_device_name(const char *dev_name, char *mac_buff) +{ + int fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); + if (fd == -1) + { + return -1; + } + + struct ifreq ifr; + memset(&ifr, 0, sizeof(struct ifreq)); + strcpy(ifr.ifr_name, dev_name); + if (ioctl(fd, SIOCGIFHWADDR, &ifr) != 0) + { + close(fd); + return -1; + } + + 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]); + close(fd); + + return 0; +} + +/****************************************************************************** + * throughput_metrics + ******************************************************************************/ + +void throughput_metrics_inc(struct throughput_metrics *iterm, uint64_t n_pkts, uint64_t n_bytes) +{ + __atomic_fetch_add(&iterm->n_bytes, n_bytes, __ATOMIC_RELAXED); + __atomic_fetch_add(&iterm->n_pkts, n_pkts, __ATOMIC_RELAXED); +} diff --git a/conf/tfe/tfe.conf b/conf/tfe/tfe.conf index 7dee4cf..1f6453f 100644 --- a/conf/tfe/tfe.conf +++ b/conf/tfe/tfe.conf @@ -2,7 +2,10 @@ nr_worker_threads=8 enable_kni_v1=0 enable_kni_v2=0 -enable_kni_v3=1 +enable_kni_v3=0 +enable_kni_v4=1 +firewall_sids=1001 +service_chaining_sids=1002 # Only when (disable_coredump == 1 || (enable_breakpad == 1 && enable_breakpad_upload == 1)) is satisfied, the core will not be generated locally disable_coredump=0 @@ -20,6 +23,8 @@ cpu_affinity_mask=1-9 # LEAST_CONN = 0; ROUND_ROBIN = 1 load_balance=1 +src_mac_addr = 00:0e:c6:d6:72:c1 + # for enable kni v3 [nfq] device=tap0 @@ -216,8 +221,52 @@ maat_redis_db_index=4 full_cfg_dir=pangu_policy/full/index/ inc_cfg_dir=pangu_policy/inc/index/ + [proxy_hits] cycle=1000 telegraf_port=8400 telegraf_ip=127.0.0.1 -app_name="proxy_rule_hits" \ No newline at end of file +app_name="proxy_rule_hits" + +# for enable kni v4 +[packet_io] +# bypass_all_traffic:1 NF2NF and SF2SF +bypass_all_traffic=0 +rx_burst_max=128 +app_symbol=sce +dev_nf_interface=eth_nf_interface + +[tap] +tap_name=tap0 + +# 1.tap_allow_mutilthread=1 load bpf rss obj +# 2.tap_allow_mutilthread=0 not load bpf rss obj +tap_allow_mutilthread=1 +bpf_obj=/opt/tsg/sapp/plug/business/kni/bpf_tun_rss_steering.o +bpf_default_queue=-1 +# tap_bpf_debug_log: cat /sys/kernel/debug/tracing/trace_pipe +bpf_debug_log=0 +# 2: BPF 使用二元组分流 +# 4: BPF 使用四元组分流 +bpf_hash_mode=2 + +# 配置 tap 网卡的 RPS +tap_rps_enable=1 +tap_rps_mask=0,1fffffff,c0000000,00000000 + +[io_uring] +enable_iouring=1 +enable_debuglog=0 +ring_size=1024 +buff_size=2048 +# io_uring_setup() flags +# IORING_SETUP_IOPOLL (1U << 0) /* io_context is polled */ +# IORING_SETUP_SQPOLL (1U << 1) /* SQ poll thread */ +# IORING_SETUP_SQ_AFF (1U << 2) /* sq_thread_cpu is valid */ +# IORING_SETUP_CQSIZE (1U << 3) /* app defines CQ size */ +# IORING_SETUP_CLAMP (1U << 4) /* clamp SQ/CQ ring sizes */ +# IORING_SETUP_ATTACH_WQ (1U << 5) /* attach to existing wq */ +# IORING_SETUP_R_DISABLED (1U << 6) /* start with ring disabled */ +# IORING_SETUP_SUBMIT_ALL (1U << 7) /* continue submit on error */ +flags=0 +sq_thread_idle=0 diff --git a/platform/CMakeLists.txt b/platform/CMakeLists.txt index 083def5..029bdc5 100644 --- a/platform/CMakeLists.txt +++ b/platform/CMakeLists.txt @@ -1,7 +1,7 @@ find_package(SYSTEMD REQUIRED) find_package(NFNETLINK REQUIRED) -add_executable(tfe src/acceptor_kni_v1.cpp src/acceptor_kni_v2.cpp src/acceptor_kni_v3.cpp src/ssl_stream.cpp src/key_keeper.cpp src/ssl_fetch_cert.cpp +add_executable(tfe src/acceptor_kni_v1.cpp src/acceptor_kni_v2.cpp src/acceptor_kni_v3.cpp src/acceptor_kni_v4.cpp src/ssl_stream.cpp src/key_keeper.cpp src/ssl_fetch_cert.cpp src/ssl_sess_cache.cpp src/ssl_sess_ticket.cpp src/ssl_service_cache.cpp src/ssl_trusted_cert_storage.cpp src/ev_root_ca_metadata.cpp src/ssl_utils.cpp src/tcp_stream.cpp src/main.cpp src/proxy.cpp src/sender_scm.cpp src/watchdog_kni.cpp src/watchdog_tfe.cpp src/ssl_ja3.cpp src/watchdog_3rd_device.cpp) @@ -27,6 +27,7 @@ target_link_libraries(tfe pthread dl nfnetlink MESA_field_stat fieldstat3 breakpad_mini + msgpack ${SYSTEMD_LIBRARIES}) if(ENABLE_PLUGIN_HTTP) diff --git a/platform/include/internal/acceptor_kni_v4.h b/platform/include/internal/acceptor_kni_v4.h new file mode 100644 index 0000000..8c0b5cb --- /dev/null +++ b/platform/include/internal/acceptor_kni_v4.h @@ -0,0 +1,13 @@ +#pragma once + +struct tfe_proxy; +struct acceptor_kni_v4 +{ + struct tfe_proxy *proxy; + const char *profile; + + struct acceptor_ctx *acceptor; +}; + +struct acceptor_kni_v4 *acceptor_kni_v4_create(struct tfe_proxy *proxy, const char *profile, void *logger); +void acceptor_kni_v4_destroy(); diff --git a/platform/include/internal/proxy.h b/platform/include/internal/proxy.h index d402385..d5cbab5 100644 --- a/platform/include/internal/proxy.h +++ b/platform/include/internal/proxy.h @@ -140,10 +140,12 @@ struct tfe_proxy unsigned int en_kni_v1_acceptor; unsigned int en_kni_v2_acceptor; unsigned int en_kni_v3_acceptor; + unsigned int en_kni_v4_acceptor; struct acceptor_kni_v1 * kni_v1_acceptor; struct acceptor_kni_v2 * kni_v2_acceptor; struct acceptor_kni_v3 * kni_v3_acceptor; + struct acceptor_kni_v4 * kni_v4_acceptor; struct sender_scm * scm_sender; struct watchdog_kni * watchdog_kni; struct watchdog_tfe * watchdog_tfe; diff --git a/platform/src/acceptor_kni_v4.cpp b/platform/src/acceptor_kni_v4.cpp new file mode 100644 index 0000000..329d8bc --- /dev/null +++ b/platform/src/acceptor_kni_v4.cpp @@ -0,0 +1,152 @@ +#include +#include +#include +#include +#include +#include // for NF_ACCEPT +#include +#include + +#include +#include +#include +#include +#include "io_uring.h" +#include "tfe_tap_rss.h" +#include "tfe_metrics.h" +#include "tfe_tcp_restore.h" +#include "acceptor_kni_v4.h" + +static void *worker_thread_cycle(void *arg) +{ + struct acceptor_thread_ctx *thread_ctx = (struct acceptor_thread_ctx *)arg; + struct packet_io *handle = thread_ctx->ref_io; + struct acceptor_ctx *acceptor_ctx = thread_ctx->ref_acceptor_ctx; + + int pkg_len = 0; + char thread_name[16]; + int n_pkt_recv_from_nf = 0; + int n_pkt_recv_from_tap = 0; + int n_pkt_recv_from_tap_c = 0; + int n_pkt_recv_from_tap_s = 0; + + snprintf(thread_name, sizeof(thread_name), "kni:worker-%d", thread_ctx->thread_index); + prctl(PR_SET_NAME, (unsigned long long)thread_name, NULL, NULL, NULL); + + if (packet_io_thread_init(handle, thread_ctx) != 0) + { + goto error_out; + } + + if (acceptor_ctx->config->enable_iouring) { + io_uring_register_read_callback(thread_ctx->tap_ctx->io_uring_fd, handle_raw_packet_from_tap, thread_ctx); + io_uring_register_read_callback(thread_ctx->tap_ctx->io_uring_c, handle_decryption_packet_from_tap, thread_ctx); + io_uring_register_read_callback(thread_ctx->tap_ctx->io_uring_s, handle_decryption_packet_from_tap, thread_ctx); + } + else { + thread_ctx->tap_ctx->buff_size = 3000; + thread_ctx->tap_ctx->buff = ALLOC(char, thread_ctx->tap_ctx->buff_size); + } + + TFE_LOG_INFO(g_default_logger, "%s: worker thread %d is running", "LOG_TAG_KNI", thread_ctx->thread_index); + + while(1) { + n_pkt_recv_from_nf = packet_io_polling_nf_interface(handle, thread_ctx->thread_index, thread_ctx); + if (acceptor_ctx->config->enable_iouring) { + n_pkt_recv_from_tap = io_uring_peek_ready_entrys(thread_ctx->tap_ctx->io_uring_fd); + n_pkt_recv_from_tap_c = io_uring_peek_ready_entrys(thread_ctx->tap_ctx->io_uring_c); + n_pkt_recv_from_tap_c = io_uring_peek_ready_entrys(thread_ctx->tap_ctx->io_uring_s); + } + else { + if ((pkg_len = tfe_tap_read_per_thread(thread_ctx->tap_ctx->tap_fd, thread_ctx->tap_ctx->buff, thread_ctx->tap_ctx->buff_size, g_default_logger)) > 0) + { + 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_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 (__atomic_fetch_add(&thread_ctx->session_table_need_reset, 0, __ATOMIC_RELAXED) > 0) + { + session_table_reset(thread_ctx->session_table); + __atomic_fetch_and(&thread_ctx->session_table_need_reset, 0, __ATOMIC_RELAXED); + } + } + +error_out: + TFE_LOG_ERROR(g_default_logger, "%s: worker thread %d exiting", LOG_TAG_SCE, thread_ctx->thread_index); + return (void *)NULL; +} + +void acceptor_kni_v4_destroy() +{ + return; +} + +struct acceptor_kni_v4 *acceptor_kni_v4_create(struct tfe_proxy *proxy, const char *profile, void *logger) +{ + int ret = 0; + struct acceptor_kni_v4 *__ctx = (struct acceptor_kni_v4 *)calloc(1, sizeof(struct acceptor_kni_v4)); + + struct acceptor_ctx *acceptor_ctx = acceptor_ctx_create(profile); + if (acceptor_ctx == NULL) + goto error_out; + + acceptor_ctx->ref_proxy = proxy; + for (int i = 0; i < acceptor_ctx->nr_worker_threads; i++) { + acceptor_ctx->work_threads[i].tid = 0; + acceptor_ctx->work_threads[i].thread_index = i; + acceptor_ctx->work_threads[i].ref_acceptor_ctx = acceptor_ctx; + + acceptor_ctx->work_threads[i].tap_ctx = tfe_tap_ctx_create(&acceptor_ctx->work_threads[i]); + if (acceptor_ctx->config->enable_iouring) { + int eventfd = 0; + struct tap_ctx *tap_ctx = acceptor_ctx->work_threads[i].tap_ctx; + tap_ctx->io_uring_fd = io_uring_instance_create(tap_ctx->tap_fd, eventfd, acceptor_ctx->config->ring_size, acceptor_ctx->config->buff_size, acceptor_ctx->config->flags, acceptor_ctx->config->sq_thread_idle, acceptor_ctx->config->enable_debuglog); + tap_ctx->io_uring_c = io_uring_instance_create(tap_ctx->tap_c, eventfd, acceptor_ctx->config->ring_size, acceptor_ctx->config->buff_size, acceptor_ctx->config->flags, acceptor_ctx->config->sq_thread_idle, acceptor_ctx->config->enable_debuglog); + tap_ctx->io_uring_s = io_uring_instance_create(tap_ctx->tap_s, eventfd, acceptor_ctx->config->ring_size, acceptor_ctx->config->buff_size, acceptor_ctx->config->flags, acceptor_ctx->config->sq_thread_idle, acceptor_ctx->config->enable_debuglog); + } + + acceptor_ctx->work_threads[i].session_table = session_table_create(); + acceptor_ctx->work_threads[i].ref_io = acceptor_ctx->io; + acceptor_ctx->work_threads[i].ref_proxy = proxy; + acceptor_ctx->work_threads[i].ref_tap_config = acceptor_ctx->config; + acceptor_ctx->work_threads[i].ref_metrics = acceptor_ctx->metrics; + acceptor_ctx->work_threads[i].session_table_need_reset = 0; + + if (acceptor_ctx->config->tap_rps_enable) + { + ret = tfe_tap_set_rps(g_default_logger, acceptor_ctx->config->tap_device, i, acceptor_ctx->config->tap_rps_mask); + if (ret != 0) + goto error_out; + } + } + + for (int i = 0; i < acceptor_ctx->nr_worker_threads; i++) { + struct acceptor_thread_ctx *thread_ctx = &acceptor_ctx->work_threads[i]; + if (pthread_create(&thread_ctx->tid, NULL, worker_thread_cycle, (void *)thread_ctx) < 0) + { + goto error_out; + } + } + + return __ctx; + +error_out: + acceptor_kni_v4_destroy(); + return NULL; +} \ No newline at end of file diff --git a/platform/src/proxy.cpp b/platform/src/proxy.cpp index 8ea88d7..df4285a 100644 --- a/platform/src/proxy.cpp +++ b/platform/src/proxy.cpp @@ -47,11 +47,14 @@ #include #include #include +#include #include #include #include #include +#include "tfe_metrics.h" + /* Breakpad */ #include @@ -532,9 +535,10 @@ void tfe_proxy_acceptor_init(struct tfe_proxy * proxy, const char * profile) { MESA_load_profile_uint_def(profile, "system", "enable_kni_v1", &proxy->en_kni_v1_acceptor, 0); MESA_load_profile_uint_def(profile, "system", "enable_kni_v2", &proxy->en_kni_v2_acceptor, 0); - MESA_load_profile_uint_def(profile, "system", "enable_kni_v3", &proxy->en_kni_v3_acceptor, 1); + MESA_load_profile_uint_def(profile, "system", "enable_kni_v3", &proxy->en_kni_v3_acceptor, 0); + MESA_load_profile_uint_def(profile, "system", "enable_kni_v4", &proxy->en_kni_v4_acceptor, 1); - int ret = proxy->en_kni_v1_acceptor + proxy->en_kni_v2_acceptor + proxy->en_kni_v3_acceptor; + int ret = proxy->en_kni_v1_acceptor + proxy->en_kni_v2_acceptor + proxy->en_kni_v3_acceptor + proxy->en_kni_v4_acceptor; CHECK_OR_EXIT((ret == 1), "Invalid KNI acceptor. Exit."); if (proxy->en_kni_v1_acceptor) @@ -555,6 +559,12 @@ void tfe_proxy_acceptor_init(struct tfe_proxy * proxy, const char * profile) CHECK_OR_EXIT(g_default_proxy->kni_v3_acceptor, "Failed at init KNIv3 acceptor. Exit. "); } + if (proxy->en_kni_v4_acceptor) + { + g_default_proxy->kni_v4_acceptor = acceptor_kni_v4_create(g_default_proxy, profile, g_default_logger); + CHECK_OR_EXIT(g_default_proxy->kni_v4_acceptor, "Failed at init KNIv4 acceptor. Exit. "); + } + return; } diff --git a/vendor/CMakeLists.txt b/vendor/CMakeLists.txt index 968182e..c750568 100644 --- a/vendor/CMakeLists.txt +++ b/vendor/CMakeLists.txt @@ -375,3 +375,19 @@ set_property(TARGET libnetfilter_queue-static PROPERTY INTERFACE_INCLUDE_DIRECTO #add_dependencies(gperftools-static gperftools) #set_property(TARGET gperftools-static PROPERTY IMPORTED_LOCATION ${INSTALL_DIR}/lib/libtcmalloc.a) #set_property(TARGET gperftools-static PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${INSTALL_DIR}/include) + +### 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 + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX= + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DMSGPACK_BUILD_TESTS=OFF) + +ExternalProject_Get_Property(msgpack-c INSTALL_DIR) +file(MAKE_DIRECTORY ${INSTALL_DIR}/include) + +add_library(msgpack STATIC IMPORTED GLOBAL) +add_dependencies(msgpack msgpack-c) +set_property(TARGET msgpack PROPERTY IMPORTED_LOCATION ${INSTALL_DIR}/lib/libmsgpack-c.a) +set_property(TARGET msgpack PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${INSTALL_DIR}/include) \ No newline at end of file diff --git a/vendor/msgpack-c-6.0.0.tar.gz b/vendor/msgpack-c-6.0.0.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..4a4a47ace4e6a568b3cc2fd555c63c043d89a474 GIT binary patch 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 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 literal 0 HcmV?d00001