From a6a80839aa29864989598cdb1feef4a1af03af86 Mon Sep 17 00:00:00 2001 From: fengweihao Date: Mon, 22 Oct 2018 11:15:57 +0800 Subject: [PATCH] =?UTF-8?q?1.=E6=B7=BB=E5=8A=A0=E4=BB=8ERedis=E8=AF=BB?= =?UTF-8?q?=E5=8F=96=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6=E5=B9=B6=E8=AF=BB?= =?UTF-8?q?=E5=8F=96=E8=AF=81=E4=B9=A6=E4=BF=A1=E6=81=AF=202.=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E8=AF=BB=E5=8F=96=E8=AF=81=E4=B9=A6=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E6=96=87=E4=BB=B6=E6=8E=A5=E5=8F=A3=EF=BC=8C?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=AF=B9der=E3=80=81P12=E8=AF=81=E4=B9=A6?= =?UTF-8?q?=E4=B8=AA=E6=95=B0=E8=8E=B7=E5=8F=96=203.=E6=B7=BB=E5=8A=A0Redi?= =?UTF-8?q?s=E9=87=8D=E8=BF=9E=E6=9C=BA=E5=88=B6=E5=8F=8A=E5=AE=8F?= =?UTF-8?q?=E5=BC=80=E5=85=B3=E6=8E=A7=E5=88=B6=E4=BF=A1=E6=81=AF=EF=BC=8C?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E5=85=B3=E9=97=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- conf/cert_store.ini | 13 +- conf/pxy_obj_keyring.json | 16 +- conf/table_info.conf | 2 +- src/Makefile | 2 +- src/cert_conf.c | 34 +++- src/cert_conf.h | 15 +- src/cert_session.c | 346 +++++++++++++++++++++++++++++---- src/cert_session.h | 2 + src/cert_store.c | 10 +- src/inc/Maat_command.h | 2 + src/inc/Maat_rule.h | 31 +-- src/inc/moodycamel_maat_rule.h | 13 +- src/lib/libMESA_htable.so | Bin 155176 -> 0 bytes src/rt/rt.mk | 1 + src/rt/rt_tmr.c | 212 ++++++++++++++++++++ src/rt/rt_tmr.h | 25 +++ 16 files changed, 639 insertions(+), 85 deletions(-) delete mode 100644 src/lib/libMESA_htable.so create mode 100644 src/rt/rt_tmr.c create mode 100644 src/rt/rt_tmr.h diff --git a/conf/cert_store.ini b/conf/cert_store.ini index 147e201..1e1ce9b 100644 --- a/conf/cert_store.ini +++ b/conf/cert_store.ini @@ -13,7 +13,9 @@ expire_after = 30 def-ca-path = ../ca [NTC_MAAT] #ÅäÖüÓÔØÄ£Ê½£¬1ΪʹÓñ¾µØjson£¬0ΪʹÓÃÅäÖ÷ַ¢ÍøÂçÏ·¢µÄÎļþ -maat_json_switch=1 +maat_json_switch=2 +#µ±¼ÓÔØÄ£Ê½ÎªÍøÂçÏ·¢Ê±£¬ÉèÖÃɨÃèÅäÖÃÐ޸ļä¸ô(s) +effective_interval=1 #Ö¸¶¨ÅäÖÿâ±íÎļþλÖà table_info=../conf/table_info.conf #ÔöÁ¿ÅäÖÃÎļþ·¾¶ @@ -25,9 +27,12 @@ pxy_obj_keyring=../conf/pxy_obj_keyring.json [LIBEVENT] #±¾µØ¼à¿Ø¶Ë¿ÚºÅ£¬Ä¬ÈÏΪ9991 port = 9991 -[REDIS] -#Ö¸¶¨Redsi·þÎñÆ÷IPµØÖ·ºÍ¶Ë¿ÚºÅ +[CERTSTORE_REDIS] ip = 127.0.0.1 port = 6379 - +[MAAT_REDIS] +#Ö¸¶¨Redsi·þÎñÆ÷IPµØÖ·ºÍ¶Ë¿ÚºÅ +ip = 192.168.11.243 +port = 6379 +dbindex = 4 diff --git a/conf/pxy_obj_keyring.json b/conf/pxy_obj_keyring.json index 04550a5..6545772 100644 --- a/conf/pxy_obj_keyring.json +++ b/conf/pxy_obj_keyring.json @@ -73,13 +73,15 @@ { "table_name": "PXY_OBJ_KEYRING", "table_content": [ - "1\t1\tname_01\troot\t/home/fengweihao/workspace/cert_store/ca/mesalab-ca-cert.key\t/home/fengweihao/workspace/cert_store/ca/mesalab-ca-cert.cer\t15\trsa2048\tnull\t1", - "2\t1\tname_02\troot\t/home/fengweihao/workspace/cert_store/ca/mesalab-ca-cert.key\t/home/fengweihao/workspace/cert_store/ca/mesalab-ca-cert.cer\t90\trsa2048\tnull\t1", - "3\t1\tname_03\troot\t/home/fengweihao/workspace/cert_store/ca/mesalab-ca-cert.key\t/home/fengweihao/workspace/cert_store/ca/mesalab-ca-cert.cer\t30\trsa2048\tnull\t1", - "4\t1\tname_04\tend-entity\t/home/fengweihao/workspace/cert_store/ca/mesalab-ca-cert.key\t/home/fengweihao/workspace/cert_store/ca/mesalab-ca-cert.cer\t30\trsa2048\tnull\t1", - "5\t1\tname_05\tintermediate\t/home/fengweihao/workspace/cert_store/ca/mesalab-ca-cert.key\t/home/fengweihao/workspace/cert_store/ca/mesalab-ca-cert.cer\t30\trsa2048\tnull\t0", - "6\t1\tname_06\tintermediate\t/home/fengweihao/workspace/cert_store/ca/mesalab-ca-cert.key\t/home/fengweihao/workspace/cert_store/ca/mesalab-ca-cert.cer\t30\trsa2048\tnull\t1", - "256\t1\tinsec\troot\t/home/fengweihao/workspace/cert_store/ca/mesalab-insec-cert.key\t/home/fengweihao/workspace/cert_store/ca/mesalab-insec-cert.cer\t30\trsa2048\tnull\t1" + "1\t1\tname_01\troot\t/test/01\t/test/01\t15\trsa2048\tURI:http://www.test.com\t1\t/home/fengweihao/workspace/cert_store/ca/mesalab-ca-cert.key\t/home/fengweihao/workspace/cert_store/ca/mesalab-ca-cert.cer", + "2\t1\tname_02\troot\t/test/01\t/test/01\t90\trsa2048\tnull\t1\t/home/fengweihao/workspace/cert_store/ca/mesalab-ca-cert.key\t/home/fengweihao/workspace/cert_store/ca/mesalab-ca-cert.cer", + "3\t1\tname_03\troot\t/test/01\t/test/01\t30\trsa2048\tnull\t1\t/home/fengweihao/workspace/cert_store/ca/mesalab-ca-cert.key\t/home/fengweihao/workspace/cert_store/ca/mesalab-ca-cert.cer", + "4\t1\tname_04\troot\t/test/01\t/test/01\t30\trsa2048\tnull\t1\t/home/fengweihao/workspace/cert_store/ca/mesalab-ca-cert.key\t/home/fengweihao/workspace/cert_store/ca/mesalab-ca-cert.cer", + "5\t1\tname_05\troot\t/test/01\t/test/01\t30\trsa2048\tnull\t1\t/home/fengweihao/workspace/cert_store/ca/mesalab-ca-cert.key\t/home/fengweihao/workspace/cert_store/ca/mesalab-ca-cert.cer", + "6\t1\tname_06\tintermediate\t/test/01\t/test/01\t30\trsa2048\tnull\t1\t/home/fengweihao/workspace/01/server.key\t/home/fengweihao/workspace/01/test01.p12", + "9\t1\tname_06\tend-entity\t/test/01\t/test/01\t30\trsa2048\tnull\t1\t/home/fengweihao/workspace/01/server.key\t/home/fengweihao/workspace/01/test02.p12", + "8\t1\tname_06\tintermediate\t/test/01\t/test/01\t30\trsa2048\tnull\t1\t/home/fengweihao/workspace/01/server.key\t/home/fengweihao/workspace/01/test02.p12", + "256\t1\tinsec\troot\t/test/01\t/test/01\t30\trsa2048\tnull\t1\t/home/fengweihao/workspace/cert_store/ca/mesalab-insec-cert.key\t/home/fengweihao/workspace/cert_store/ca/mesalab-insec-cert.cer" ] } ] diff --git a/conf/table_info.conf b/conf/table_info.conf index c04b50c..e82d0f8 100644 --- a/conf/table_info.conf +++ b/conf/table_info.conf @@ -17,4 +17,4 @@ #id name type src_charset dst_charset do_merge cross_cache quick_mode 1 COMPILE compile 2 GROUP group -3 PXY_OBJ_KEYRING plugin 8 +3 PXY_OBJ_KEYRING plugin {"valid":10,"foreign":"11,12"} diff --git a/src/Makefile b/src/Makefile index 6163a7b..2f03453 100644 --- a/src/Makefile +++ b/src/Makefile @@ -51,7 +51,7 @@ OBJS += $(OBJS_$(dir)) LDFLAGS_GLOBAL += -L ./lib -lapps -lcrypto -lssl -levent -lhiredis LDFLAGS_GLOBAL += -L ./lib -lMESA_htable -lMESA_field_stat2 -lMESA_handle_logger -lMESA_prof_load LDFLAGS_GLOBAL += \ - -lpthread -lcrypt -lmaatframe -lm -lz -ldl -lstdc++ + -lpthread -lcrypt -lm -lz -ldl -lmaatframe -lstdc++ CFLAGS_LOCAL = -std=gnu99 -g -O3 -W -Wall \ -I.\ diff --git a/src/cert_conf.c b/src/cert_conf.c index c9106c3..5d7835c 100644 --- a/src/cert_conf.c +++ b/src/cert_conf.c @@ -22,7 +22,8 @@ struct config_bucket_t certConfig = { .thread_nu = 1, .expire_after = 30, .def_path = "/home/test", - .addr_t = {9995, 3336, "0.0.0.0"}, + .addr_t = {9995, 6379, "0.0.0.0", 0, 6379, "0.0.0.0"}, + .keyring = {0, 0, NULL, NULL}, }; struct config_bucket_t *cert_default_config() @@ -68,17 +69,36 @@ static int load_module_config(char *config) goto finish; } - xret = MESA_load_profile_string_nodef(config, "REDIS", "ip", rte->addr_t.r_ip, 16); + xret = MESA_load_profile_string_nodef(config, "MAAT_REDIS", "ip", rte->addr_t.maat_ip, 16); if (xret < 0){ - mesa_runtime_log(RLOG_LV_FATAL, MODULE_NAME, "Ip invalid"); + mesa_runtime_log(RLOG_LV_FATAL, MODULE_NAME, "Maat redis ip invalid"); goto finish; } - xret = MESA_load_profile_short_nodef(config, "REDIS", "port", (short *)&(rte->addr_t.r_port)); + xret = MESA_load_profile_short_nodef(config, "MAAT_REDIS", "port", (short *)&(rte->addr_t.maat_port)); if (xret < 0){ - mesa_runtime_log(RLOG_LV_FATAL, MODULE_NAME, "Redis Port invalid"); + mesa_runtime_log(RLOG_LV_FATAL, MODULE_NAME, "Maat redis port invalid"); goto finish; } + + xret = MESA_load_profile_short_nodef(config, "MAAT_REDIS", "dbindex", (short *)&(rte->addr_t.dbindex)); + if (xret < 0){ + mesa_runtime_log(RLOG_LV_FATAL, MODULE_NAME, "Maat redis dbindex invalid"); + goto finish; + } + + xret = MESA_load_profile_string_nodef(config, "CERTSTORE_REDIS", "ip", rte->addr_t.store_ip, 16); + if (xret < 0){ + mesa_runtime_log(RLOG_LV_FATAL, MODULE_NAME, "Certsotre redis ip invalid"); + goto finish; + } + + xret = MESA_load_profile_short_nodef(config, "CERTSTORE_REDIS", "port", (short *)&(rte->addr_t.store_port)); + if (xret < 0){ + mesa_runtime_log(RLOG_LV_FATAL, MODULE_NAME, "Certsotre redis port invalid"); + goto finish; + } + finish: return xret; } @@ -111,6 +131,10 @@ static int load_maat_config(char *config) } if (maat_t->maat_json_switch == 0){ + xret = MESA_load_profile_uint_nodef(config, "NTC_MAAT", "effective_interval", &(maat_t->effective_interval_s)); + if (xret < 0){ + mesa_runtime_log(RLOG_LV_FATAL, MODULE_NAME, "Reading the interval of scan failed"); + } xret = MESA_load_profile_string_nodef(config, "NTC_MAAT", "inc_cfg_dir", maat_t->inc_cfg_dir, 128); if (xret < 0 && !rt_file_exsit( maat_t->inc_cfg_dir)){ mesa_runtime_log(RLOG_LV_FATAL, MODULE_NAME, "Read the table info failed or the (%s) does not exist", diff --git a/src/cert_conf.h b/src/cert_conf.h index 8f710e1..f7e16c8 100644 --- a/src/cert_conf.h +++ b/src/cert_conf.h @@ -40,22 +40,29 @@ struct pxy_obj_keyring{ char public_algo[256]; uint64_t expire_after; int is_valid; + STACK_OF(X509) *stack_ca; }; struct key_ring_list { + int updata_type; uint64_t sum_cnt; MESA_htable_handle htable, oldhtable; }; struct _initer_addr_t{ - uint16_t e_port; /* libevent prot*/ - uint16_t r_port; /* redis port*/ - char r_ip[16]; /* redis ip */ + uint16_t e_port; /*libevent prot*/ + uint16_t maat_port; /*maat redis port*/ + char maat_ip[16]; /*maat redis ip */ + int dbindex; /*maat redis dbindex*/ + + uint16_t store_port; /*store redis port */ + char store_ip[16]; /*store redis ip*/ }; struct ntc_maat_t{ - unsigned int maat_json_switch; + unsigned int maat_json_switch; + unsigned int effective_interval_s; char info_path[128]; char pxy_path[128]; char inc_cfg_dir[128]; diff --git a/src/cert_session.c b/src/cert_session.c index 3ae232d..fd47283 100644 --- a/src/cert_session.c +++ b/src/cert_session.c @@ -21,12 +21,14 @@ #include #include #include +#include #include "rt_string.h" #include "rt_common.h" #include "rt_stdlib.h" #include "rt_file.h" #include "rt_time.h" +#include "rt_tmr.h" #include "json.h" #include "cert_conf.h" @@ -47,11 +49,18 @@ #define WAIT_FOR_EFFECTIVE_US 1000*1000 -#define SG_DATA_SIZE 4096 +#define SG_DATA_SIZE 8192 #define SG_INSEC_ID 256 -#define DEFAULT_PRIVATEKEY_NAME "mesalab-def-cert.key" -#define DEFAULT_CA_CERTIFICATE "mesalab-def-cert.cer" +#define LOCAL_USER_PEN 1 +#define LOCAL_USER_DER 2 +#define LOCAL_USER_P12 3 + +#define DEFAULT_PRIVATEKEY_NAME "mesalab-ca-cert.key" +#define DEFAULT_CA_CERTIFICATE "mesalab-ca-cert.cer" + +#define CM_UPDATE_TYPE_FULL 1 +#define CM_UPDATE_TYPE_INC 2 static libevent_thread *threads; @@ -168,6 +177,146 @@ err: return 0; } +static X509* base_load_pkcs12(BIO *in, EVP_PKEY **pkey, X509 **x, STACK_OF(X509) **ca) +{ + PKCS12 *p12; + const char *pass = ""; + + X509 *_x = NULL; + EVP_PKEY *_pkey; + STACK_OF(X509) *_ca = NULL; + + OpenSSL_add_all_algorithms(); + ERR_load_crypto_strings(); + + p12 = d2i_PKCS12_bio(in, NULL); + if (p12 == NULL) { + mesa_runtime_log(RLOG_LV_FATAL, MODULE_NAME, "Error loading PKCS12 file"); + goto finish; + } + if (!PKCS12_parse(p12, pass, &_pkey, &_x, &_ca)) { + mesa_runtime_log(RLOG_LV_FATAL, MODULE_NAME, "Error parsing PKCS#12 file"); + goto finish; + } + + if (x) + *x = _x; + if (pkey) + *pkey = _pkey; + if (ca) + *ca = _ca; + + finish: + if (p12) + PKCS12_free(p12); + return _x; +} + +static X509 * +cert_base_load_x509 (BIO * cert, STACK_OF(X509) **stack_ca, int iFormat) +{ + X509 *x = NULL; + + switch (iFormat) + { + case LOCAL_USER_DER: + x = d2i_X509_bio (cert, NULL); + break; + case LOCAL_USER_PEN: + x = PEM_read_bio_X509 (cert, NULL, NULL, NULL); + break; + case LOCAL_USER_P12: + x = base_load_pkcs12(cert, NULL, &x, stack_ca); + break; + default: + break; + } + return x; +} + +static X509 * +cert_load_x509(char *file, STACK_OF(X509) **stack_ca) +{ + BIO *in = NULL; + X509 *x509 = NULL; + + if(!file){ + mesa_runtime_log(RLOG_LV_FATAL, MODULE_NAME, "Input cert file is empty."); + goto finish; + } + + if ((in = BIO_new(BIO_s_file())) == NULL) { + mesa_runtime_log(RLOG_LV_FATAL, MODULE_NAME, "Bio malloc failed."); + goto finish; + } + if (BIO_read_filename(in, file) <= 0) { + mesa_runtime_log(RLOG_LV_FATAL, MODULE_NAME, "Error opening %s", file); + goto finish; + } + + /**try pem */ + if ((x509 = cert_base_load_x509(in, stack_ca, LOCAL_USER_PEN)) != NULL) + goto end; + (void)BIO_reset (in); + if ((x509 = cert_base_load_x509(in, stack_ca, LOCAL_USER_P12)) != NULL) + goto end; + (void)BIO_reset (in); + if ((x509 = cert_base_load_x509(in, stack_ca, LOCAL_USER_DER)) != NULL) + goto end; +end: + BIO_free (in); + in = NULL; +finish: + return x509; +} + +EVP_PKEY * cert_base_key_x509 (BIO * bio, int iFormat, char *strPwd) +{ + EVP_PKEY *pkey = NULL; + + switch (iFormat){ + case LOCAL_USER_PEN: + pkey = PEM_read_bio_PrivateKey (bio, NULL, NULL, strPwd); + break; + case LOCAL_USER_P12: + base_load_pkcs12(bio, &pkey, NULL, NULL); + break; + default: + break; + } + + return pkey; +} + +EVP_PKEY * cert_load_key(char *keyfile) +{ + EVP_PKEY *pkey = NULL; + BIO *in = NULL; + + if(!keyfile){ + mesa_runtime_log(RLOG_LV_FATAL, MODULE_NAME, "Input key file is empty."); + goto finish; + } + if ((in = BIO_new(BIO_s_file())) == NULL) { + mesa_runtime_log(RLOG_LV_FATAL, MODULE_NAME, "Bio malloc failed."); + goto finish; + } + if (BIO_read_filename(in, keyfile) <= 0) { + mesa_runtime_log(RLOG_LV_FATAL, MODULE_NAME, "Error opening %s\n", keyfile); + goto finish; + } + + if ((pkey = cert_base_key_x509 (in, LOCAL_USER_PEN, "")) != NULL) + goto finish; + (void)BIO_reset (in); + if ((pkey = cert_base_key_x509 (in, LOCAL_USER_P12, "")) != NULL) + goto finish; +finish: + if (in != NULL) + BIO_free (in); + return pkey; +} + static void key_ring_free(void *data) { struct pxy_obj_keyring *pxy_obj = NULL; @@ -265,6 +414,8 @@ ssl_x509_v3ext_copy_by_nid(X509 *crt, X509 *origcrt, int nid) return 1; } + +/**todo Use rules to determine if an sni exists */ static int x509_alt_name_cmp(unsigned char *name, char *extraname) { @@ -656,7 +807,7 @@ int redis_rsync_init(struct event_base *base, struct redisAsyncContext **cl_ctx) int xret = -1; struct config_bucket_t *redis = cert_default_config(); - *cl_ctx = redisAsyncConnect(redis->addr_t.r_ip, redis->addr_t.r_port); + *cl_ctx = redisAsyncConnect(redis->addr_t.store_ip, redis->addr_t.store_port); if((*cl_ctx)->err ) { mesa_runtime_log(RLOG_LV_FATAL, MODULE_NAME, "Redis Connect error : %s", (*cl_ctx)->errstr); goto finish; @@ -825,14 +976,18 @@ err: static int x509_online_append(struct x509_object_ctx *def, X509 *origin, int id, - char *sni, char *root, char *sign, char *pkey) + char *sni, char *root, char *sign, + char *pkey, STACK_OF(X509) **stack_ca) { void *odata = NULL; int _expire = 0; char *_crl = NULL; X509 *_root = NULL; EVP_PKEY *_key = NULL; struct key_ring_list *keyring = &cert_default_config()->keyring; - + if (keyring->htable == NULL){ + mesa_runtime_log(RLOG_LV_FATAL, MODULE_NAME, "The approval certificate chain is empty"); + goto finish; + } odata = MESA_htable_search(keyring->htable, (const uchar *)&id, sizeof(int)); if ( !odata ){ _root = def->root; @@ -842,19 +997,23 @@ x509_online_append(struct x509_object_ctx *def, X509 *origin, int id, } else { struct pxy_obj_keyring *pxy_obj = (struct pxy_obj_keyring *)odata; if (pxy_obj->is_valid != 1){ - id = SG_INSEC_ID; - odata = MESA_htable_search(keyring->htable, (const uchar *)&id, sizeof(int)); - if ( !odata){ - mesa_runtime_log(RLOG_LV_INFO, MODULE_NAME, "Read insecure certificate failed"); - goto finish; - } - pxy_obj = (struct pxy_obj_keyring *)odata; + pxy_obj->root = def->root; + pxy_obj->key = def->key; + }else{ if (!STRCMP(pxy_obj->type, "end-entity")){ + mesa_runtime_log(RLOG_LV_INFO, MODULE_NAME, "The certificate(%d) type is an entity certificate", + pxy_obj->id); + *stack_ca = pxy_obj->stack_ca; x509_get_msg_from_ca(pxy_obj->root, sign); x509_get_private_key(pxy_obj->key, pkey); goto finish; } + if (!STRCMP(pxy_obj->type, "intermediate")){ + mesa_runtime_log(RLOG_LV_INFO, MODULE_NAME, "The certificate(%d) type is intermediate, chain address %p", + pxy_obj->id, pxy_obj->stack_ca); + *stack_ca = pxy_obj->stack_ca; + } } _root = pxy_obj->root; _key = pxy_obj->key; @@ -994,8 +1153,9 @@ web_json_table_add(char *privatekey, char *sign, static int redis_clnt_pdu_send(struct request_t *request, redisAsyncContext *c) { - int xret = -1; + int xret = -1, i = 0; int expire_after; + STACK_OF(X509) *stack_ca = NULL; uint64_t startTime = 0, endTime = 0; libevent_thread *info = threads + request->thread_id; char sign[SG_DATA_SIZE] = {0}, pkey[SG_DATA_SIZE] = {0}; @@ -1004,7 +1164,7 @@ redis_clnt_pdu_send(struct request_t *request, redisAsyncContext *c) startTime = rt_time_ns(); expire_after = x509_online_append(&info->def, request->origin, request->keyring_id, request->sni, - root, sign, pkey); + root, sign, pkey, &stack_ca); if (sign[0] == '\0' && pkey[0] == '\0'){ mesa_runtime_log(RLOG_LV_FATAL, MODULE_NAME, "Failed to sign certificate"); evhttp_send_error(request->evh_req, HTTP_NOTFOUND, 0); @@ -1018,10 +1178,22 @@ redis_clnt_pdu_send(struct request_t *request, redisAsyncContext *c) FS_internal_operate(SGstats.handle, info->column_ids, SGstats.line_ids[3], FS_OP_SET, info->diffTime); FS_internal_operate(SGstats.handle, info->field_ids, 0, FS_OP_ADD, 1); - printf("%s\n", sign); - char *chain[6] ={0}; - chain[0] = root; - chain[1] = sign; + char _chain[4][SG_DATA_SIZE]; + char *chain[6] = {0}; + if (stack_ca){ + for (i = 0; i < sk_X509_num(stack_ca); i++){ + x509_get_msg_from_ca(sk_X509_value(stack_ca, i), _chain[i]); + chain[i] = _chain[i]; + } + if (root[0] != '\0'){ + chain[i] = root; + i++; + } + chain[i] = sign; + }else{ + chain[0] = root; + chain[1] = sign; + } web_json_table_add(pkey, sign, chain, &request->odata); if (NULL == c){ @@ -1095,6 +1267,23 @@ void redis_get_callback(redisAsyncContext *c, void *r, void *privdata) return; } +int x509_privatekey_init2(char * private_file, char * public_file, + EVP_PKEY **key, X509 **root, STACK_OF(X509) **stack_ca) +{ + if ((*root = cert_load_x509(public_file, stack_ca)) == NULL ){ + mesa_runtime_log(RLOG_LV_FATAL, MODULE_NAME, "Application for x509 failed"); + goto finish; + } + + if ((*key = cert_load_key(private_file)) == NULL){ + mesa_runtime_log(RLOG_LV_FATAL, MODULE_NAME, "Private key read failed"); + goto finish; + } + +finish: + return 0; +} + int x509_privatekey_init(char *private_file, char *public_file, EVP_PKEY **key, X509 **root) { @@ -1362,7 +1551,7 @@ int redis_sync_init(struct redisContext **c) struct timeval timeout = { 1, 500000 }; // 1.5 seconds - *c = redisConnectWithTimeout(redis->addr_t.r_ip, redis->addr_t.r_port, timeout); + *c = redisConnectWithTimeout(redis->addr_t.store_ip, redis->addr_t.store_port, timeout); if (*c == NULL || (*c)->err) { if (*c) { mesa_runtime_log(RLOG_LV_FATAL, MODULE_NAME, "Sync connection error: %s", (*c)->errstr); @@ -1428,6 +1617,7 @@ static void *pthread_worker_libevent(void *arg) mesa_runtime_log(RLOG_LV_FATAL, MODULE_NAME, "couldn'thread create evhttp. Exiting."); goto error; } + thread->base = base; /* Context initialization */ xret = task_private_init(base, thread); @@ -1503,6 +1693,40 @@ fs_screen_preview(libevent_thread *thread) return 0; } +static void +redis_link_detection(uint32_t __attribute__((__unused__)) uid, + int __attribute__((__unused__))argc, + char **argv) +{ + int tid = 0, xret = 0; + libevent_thread *info = NULL; + libevent_thread *threads = (libevent_thread *)argv; + + unsigned int thread_nu = cert_default_config()->thread_nu; + for (tid = 0; tid < (int)thread_nu; tid++) { + info = threads + tid; + if(info->cl_ctx->err != 0){ + if (info->sync) + redisFree(info->sync); + + xret = redis_sync_init(&info->sync); + if (xret < 0 || !info->sync){ + mesa_runtime_log(RLOG_LV_FATAL, MODULE_NAME, "[%d]trying to connect sync redis failed", tid); + continue; + }else{ + mesa_runtime_log(RLOG_LV_FATAL, MODULE_NAME, "[%d]trying to connect sync redis success", tid); + } + + xret = redis_rsync_init(info->base, &info->cl_ctx); + if (xret < 0 || !info->cl_ctx){ + mesa_runtime_log(RLOG_LV_FATAL, MODULE_NAME, "[%d]trying to connect rsync redis failed", tid); + }else{ + mesa_runtime_log(RLOG_LV_FATAL, MODULE_NAME, "[%d]trying to connect rsync redis success", tid); + } + } + } +} + static int libevent_socket_init() { @@ -1549,7 +1773,17 @@ libevent_socket_init() goto finish; } } - +#ifdef RT_TMR_ADVANCED + /*Create timers to monitor redis connections **/ + uint32_t tm_link_detetion = 0; + tm_link_detetion = tmr_create(1, "Redis link detection", + redis_link_detection, 1, (char **)threads, 5); + if (((int32_t)tm_link_detetion < 0)){ + mesa_runtime_log(RLOG_LV_FATAL, MODULE_NAME, "%s", + "Can not create link-detection timer for redis\n"); + } + tmr_start(tm_link_detetion); +#endif FOREVER{ sleep(1); } @@ -1585,8 +1819,10 @@ void sigproc(int __attribute__((__unused__))sig) thread = threads + tid; if (thread->sync){ redisAsyncDisconnect(thread->cl_ctx); + free(thread->cl_ctx); redisFree(thread->sync); } + event_base_free(thread->base); key_ring_list_destroy(rte->keyring.htable); key_ring_list_destroy(rte->keyring.oldhtable); } @@ -1641,12 +1877,12 @@ static int mesa_fiel_stat_init() void Maat_read_entry_start_cb(int update_type, void* u_para) { -#define CM_UPDATE_TYPE_FULL 1 -#define CM_UPDATE_TYPE_INC 2 struct key_ring_list *keyring = (struct key_ring_list *)u_para; - if (update_type != CM_UPDATE_TYPE_FULL) + if (update_type != CM_UPDATE_TYPE_FULL){ + keyring->updata_type = 2; goto finish; + } if (keyring->oldhtable) key_ring_list_destroy(keyring->oldhtable); @@ -1654,6 +1890,7 @@ void Maat_read_entry_start_cb(int update_type, void* u_para) /*Keyring list initialization **/ keyring->oldhtable = key_ring_list_create(); keyring->sum_cnt = 0; + keyring->updata_type = 1; mesa_runtime_log(RLOG_LV_INFO, MODULE_NAME, "The initial key ring list was successful, addr is %p", keyring->oldhtable); finish: @@ -1666,6 +1903,9 @@ Maat_read_entry_cb(int __attribute__((__unused__))table_id, const char* table_li { int xret = 0; struct pxy_obj_keyring *pxy_obj = NULL; + MESA_htable_handle htable = NULL; + char __attribute__((__unused__))_priv_file[512] = {0}; + char __attribute__((__unused__))_publi_file[512] = {0}; char private_file[512] = {0}, public_file[512] = {0}; struct key_ring_list *keyring = (struct key_ring_list *)u_para; @@ -1676,19 +1916,34 @@ Maat_read_entry_cb(int __attribute__((__unused__))table_id, const char* table_li goto finish; } memset(pxy_obj, 0, sizeof(struct pxy_obj_keyring)); - sscanf(table_line, "%d\t%d\t%s\t%s\t%s\t%s\t%lu\t%s\t%s\t%d", &pxy_obj->id, &pxy_obj->service, pxy_obj->name, - pxy_obj->type, private_file, public_file, &pxy_obj->expire_after, pxy_obj->public_algo, - pxy_obj->ctl, &pxy_obj->is_valid); - xret = x509_privatekey_init(private_file, public_file, &pxy_obj->key, &pxy_obj->root); - if (xret < 0 || !pxy_obj->key || !pxy_obj->root){ - mesa_runtime_log(RLOG_LV_FATAL, MODULE_NAME, "Failed to initialize the x509 certificate, the keyring id is %d", - pxy_obj->id); - goto finish; + if (keyring->updata_type == CM_UPDATE_TYPE_FULL){ + htable = keyring->oldhtable; + }else{ + htable = keyring->htable; } - MESA_htable_add(keyring->oldhtable, (const uchar *)(&(pxy_obj->id)), sizeof(int), pxy_obj); - keyring->sum_cnt++; + sscanf(table_line, "%d\t%d\t%s\t%s\t%s\t%s\t%lu\t%s\t%s\t%d\t%s\t%s", &pxy_obj->id, &pxy_obj->service, pxy_obj->name, + pxy_obj->type, _priv_file, _publi_file, &pxy_obj->expire_after, pxy_obj->public_algo, + pxy_obj->ctl, &pxy_obj->is_valid, private_file, public_file); + + if (pxy_obj->is_valid){ + xret = x509_privatekey_init2(private_file, public_file, &pxy_obj->key, &pxy_obj->root, &pxy_obj->stack_ca); + if (xret < 0 || !pxy_obj->key || !pxy_obj->root){ + mesa_runtime_log(RLOG_LV_FATAL, MODULE_NAME, "Failed to initialize the x509 certificate, the keyring id is %d", + pxy_obj->id); + goto finish; + } + mesa_runtime_log(RLOG_LV_INFO, MODULE_NAME, "initialize the x509 certificate, the keyring id is %d", + pxy_obj->id); + + MESA_htable_add(htable, (const uchar *)(&(pxy_obj->id)), sizeof(int), pxy_obj); + keyring->sum_cnt++; + }else{ + mesa_runtime_log(RLOG_LV_INFO, MODULE_NAME, "Unapprove keyring id is %d", + pxy_obj->id); + MESA_htable_del(htable, (const uchar *)(&(pxy_obj->id)), sizeof(int), key_ring_free); + } finish: return; @@ -1699,9 +1954,11 @@ void Maat_read_entry_finish_cb(void* u_para) MESA_htable_handle tmphtable = NULL; struct key_ring_list *keyring = (struct key_ring_list *)u_para; - tmphtable = keyring->htable; - keyring->htable = keyring->oldhtable; - keyring->oldhtable = tmphtable; + if (keyring->updata_type == CM_UPDATE_TYPE_FULL){ + tmphtable = keyring->htable; + keyring->htable = keyring->oldhtable; + keyring->oldhtable = tmphtable; + } return; } @@ -1727,13 +1984,14 @@ int sample_plugin_table(Maat_feather_t feather,const char* table_name, int maat_feather_init() { - int wait_second = 0; Maat_feather_t feather = NULL; - int scan_interval_ms = 1, effective_interval_ms = 0; + int scan_interval_ms = 1000; struct config_bucket_t *rte = cert_default_config(); struct ntc_maat_t *maat_t = &rte->maat_t; + int effective_interval_ms = maat_t->effective_interval_s * 1000; + feather = Maat_inter_feather(rte->thread_nu, maat_t->info_path, logging_sc_lid.run_log_handle); Maat_inter_set_feather_opt(feather, MAAT_OPT_INSTANCE_NAME, "certstore", strlen("certstore") + 1); @@ -1744,11 +2002,18 @@ int maat_feather_init() if (maat_t->maat_json_switch == 0){ Maat_inter_set_feather_opt(feather, MAAT_OPT_FULL_CFG_DIR, maat_t->full_cfg_dir, strlen(maat_t->full_cfg_dir)+1); Maat_inter_set_feather_opt(feather, MAAT_OPT_INC_CFG_DIR, maat_t->inc_cfg_dir, strlen(maat_t->inc_cfg_dir)+1); - wait_second = 14; + } + if (maat_t->maat_json_switch == 2){ + Maat_inter_set_feather_opt(feather, MAAT_OPT_REDIS_IP, rte->addr_t.maat_ip, strlen(rte->addr_t.maat_ip)+1); + Maat_inter_set_feather_opt(feather, MAAT_OPT_REDIS_PORT, &rte->addr_t.maat_port, sizeof(rte->addr_t.maat_port)); + Maat_inter_set_feather_opt(feather, MAAT_OPT_REDIS_INDEX, &rte->addr_t.dbindex, sizeof(rte->addr_t.dbindex)); } Maat_inter_set_feather_opt(feather, MAAT_OPT_SCANDIR_INTERVAL_MS,&scan_interval_ms, sizeof(scan_interval_ms)); Maat_inter_set_feather_opt(feather, MAAT_OPT_EFFECT_INVERVAL_MS,&effective_interval_ms, sizeof(effective_interval_ms)); + /***/ + const char* foregin_dir="./foreign_files/"; + Maat_inter_set_feather_opt(feather, MAAT_OPT_FOREIGN_CONT_DIR,foregin_dir, strlen(foregin_dir)+1); Maat_inter_initiate_feather(feather); sample_plugin_table(feather, "PXY_OBJ_KEYRING", @@ -1757,7 +2022,6 @@ int maat_feather_init() Maat_read_entry_finish_cb, &rte->keyring, NULL); - sleep(wait_second); return 0; } diff --git a/src/cert_session.h b/src/cert_session.h index 2a86128..c0c5850 100644 --- a/src/cert_session.h +++ b/src/cert_session.h @@ -27,6 +27,8 @@ typedef struct { rt_pthread_attr *attr; + struct event_base *base; + struct x509_object_ctx def; struct redisAsyncContext *cl_ctx; diff --git a/src/cert_store.c b/src/cert_store.c index 5b1ae0d..90a41a7 100644 --- a/src/cert_store.c +++ b/src/cert_store.c @@ -25,7 +25,7 @@ /* Configure Path */ #if 0 -#define CERT_BASIC_CFG "/usr/local/etc/cert_store.yaml" +#define CERT_BASIC_CFG "/home/ceiec/certstore_run/conf/cert_store.ini" #else #define CERT_BASIC_CFG "../conf/cert_store.ini" #endif @@ -81,8 +81,11 @@ void cert_preview () printf("\r\nBasic Configuration of CertStore \n"); printf("%30s:%45d\n", "The Threads", rte->thread_nu); - printf("%30s:%45s\n", "Redis Ip", rte->addr_t.r_ip); - printf("%30s:%45d\n", "Redis Port", rte->addr_t.r_port); + printf("%30s:%45s\n", "Store Redis Ip", rte->addr_t.store_ip); + printf("%30s:%45d\n", "Store Redis Port", rte->addr_t.store_port); + printf("%30s:%45s\n", "Maat Redis Ip", rte->addr_t.maat_ip); + printf("%30s:%45d\n", "Maat Redis Port", rte->addr_t.maat_port); + printf("%30s:%45d\n", "Maat Redis index", rte->addr_t.dbindex); printf("%30s:%45d\n", "Libevent Port", rte->addr_t.e_port); printf("%30s:%45s\n", "Def Cert Path", rte->def_path); printf("%30s:%45s\n", "Log Directory", logging_sc_lid.run_log_path); @@ -91,6 +94,7 @@ void cert_preview () printf("%30s:%45s\n", "Pxy Obj Keyring", rte->maat_t.pxy_path); } if (rte->maat_t.maat_json_switch == 0){ + printf("%30s:%45d\n", "Scan Interval", rte->maat_t.effective_interval_s); printf("%30s:%45s\n", "Full Cfg Path", rte->maat_t.full_cfg_dir); printf("%30s:%45s\n", "Inc Cfg Path", rte->maat_t.inc_cfg_dir); diff --git a/src/inc/Maat_command.h b/src/inc/Maat_command.h index fbdd9a1..ec4796f 100644 --- a/src/inc/Maat_command.h +++ b/src/inc/Maat_command.h @@ -156,6 +156,8 @@ int Maat_cmd_set_group(Maat_feather_t feather, int group_id, const struct Maat_r //Return -1 for failed. int Maat_cmd_set_line(Maat_feather_t feather,const struct Maat_line_t* line_rule, enum MAAT_OPERATION op); int Maat_cmd_set_lines(Maat_feather_t feather,const struct Maat_line_t** line_rule, int line_num ,enum MAAT_OPERATION op); +int Maat_cmd_set_file(Maat_feather_t feather,const char* key, const char* value, size_t size, enum MAAT_OPERATION op); + //Return the value of key after the increment. //If the key does not exist, it is set to 0 before performing the operation. long long Maat_cmd_incrby(Maat_feather_t feather,const char* key, int increment); diff --git a/src/inc/Maat_rule.h b/src/inc/Maat_rule.h index bcb1546..91d373c 100644 --- a/src/inc/Maat_rule.h +++ b/src/inc/Maat_rule.h @@ -2,11 +2,11 @@ /* *****************Maat Network Flow Rule Manage Framework******** * Maat is the Goddess of truth and justice in ancient Egyptian concept. -* Her feather was the measure that determined whether the souls (considered +* Her feather was the measure that determined whether the souls (considered * to reside in the heart) of the departed would reach the paradise of afterlife * successfully. * Author: zhengchao@iie.ac.cn,MESA -* Version 2018-07-27 huge service_define +* Version 2018-09-25 foreign key and rule tags. * NOTE: MUST compile with G++ * All right reserved by Institute of Infomation Engineering,Chinese Academic of Science 2014~2018 ********************************************************* @@ -105,17 +105,17 @@ struct sub_item_pos_t struct Maat_region_pos_t { - + int region_id; int sub_item_num; - struct sub_item_pos_t sub_item_pos[MAAT_MAX_EXPR_ITEM_NUM]; + struct sub_item_pos_t sub_item_pos[MAAT_MAX_EXPR_ITEM_NUM]; }; struct Maat_hit_detail_t { int config_id;//set <0 if half hit; int hit_region_cnt; - struct Maat_region_pos_t region_pos[MAAT_MAX_HIT_RULE_NUM]; + struct Maat_region_pos_t region_pos[MAAT_MAX_HIT_RULE_NUM]; }; //--------------------HITTING DETAIL DESCRIPTION END @@ -125,7 +125,7 @@ Maat_feather_t Maat_summon_feather(int max_thread_num, const char* ful_cfg_dir, const char* inc_cfg_dir, void*logger);//MESA_handle_logger -//Abondon interface ,left for compatible. +//Abondon interface ,left for compatible. Maat_feather_t Maat_summon_feather_json(int max_thread_num, const char* table_info_path, const char* json_rule, @@ -141,10 +141,10 @@ enum MAAT_INIT_OPT MAAT_OPT_FULL_CFG_DIR, //VALUE is a const char*, MUST end with '\0', SIZE= strlen(string+'\0')+1.DEFAULT: no default. MAAT_OPT_INC_CFG_DIR, //VALUE is a const char*, MUST end with '\0', SIZE= strlen(string+'\0')+1.DEFAULT: no default. MAAT_OPT_JSON_FILE_PATH, //VALUE is a const char*, MUST end with '\0', SIZE= strlen(string+'\0')+1.DEFAULT: no default. - MAAT_OPT_STAT_ON, //VALUE is NULL,SIZE is 0. MAAT_OPT_STAT_FILE_PATH must be set. Default: stat OFF. - MAAT_OPT_PERF_ON, //VALUE is NULL,SIZE is 0. MAAT_OPT_STAT_FILE_PATH must be set. Default: stat OFF. + MAAT_OPT_STAT_ON, //VALUE is NULL, SIZE is 0. MAAT_OPT_STAT_FILE_PATH must be set. Default: stat OFF. + MAAT_OPT_PERF_ON, //VALUE is NULL, SIZE is 0. MAAT_OPT_STAT_FILE_PATH must be set. Default: stat OFF. MAAT_OPT_STAT_FILE_PATH, //VALUE is a const char*, MUST end with '\0', SIZE= strlen(string+'\0')+1. DEFAULT: no default. - MAAT_OPT_SCAN_DETAIL, //VALUE is interger *, SIZE=sizeof(int). 0: not return any detail;1: return hit pos, not include regex grouping; + MAAT_OPT_SCAN_DETAIL, //VALUE is interger *, SIZE=sizeof(int). 0: not return any detail;1: return hit pos, not include regex grouping. // 2 return hit pos and regex grouping pos;DEFAULT:0 MAAT_OPT_INSTANCE_NAME, //VALUE is a const char*, MUST end with '\0', SIZE= strlen(string+'\0')+1, no more than 11 bytes.DEFAULT: MAAT_$tableinfo_path$. MAAT_OPT_DECRYPT_KEY, //VALUE is a const char*, MUST end with '\0', SIZE= strlen(string+'\0')+1. No DEFAULT. @@ -154,15 +154,18 @@ enum MAAT_INIT_OPT MAAT_OPT_CMD_AUTO_NUMBERING, //VALUE is a interger *, 1 or 0, SIZE=sizeof(int). DEFAULT: 1. MAAT_OPT_DEFERRED_LOAD, //VALUE is NULL,SIZE is 0. Default: Deffered initialization OFF. MAAT_OPT_CUMULATIVE_UPDATE_OFF, //VALUE is NULL,SIZE is 0. Default: CUMMULATIVE UPDATE ON. - MAAT_OPT_LOAD_VERSION_FROM, //VALUE is a long long, SIZE=sizeof(long long). Default: Load the Latest. Only valid in redis mode, and maybe failed for too old. + MAAT_OPT_LOAD_VERSION_FROM, //VALUE is a long long, SIZE=sizeof(long long). Default: Load the Latest. Only valid in redis mode, and maybe failed for too old. //This option also disables background update. - MAAT_OPT_ENABLE_UPDATE //VALUE is interger, SIZE=sizeof(int). 1: Enabled, 0:Disabled. DEFAULT: Backgroud update is enabled. Runtime setting is allowed. -}; + MAAT_OPT_ENABLE_UPDATE, //VALUE is interger, SIZE=sizeof(int). 1: Enabled, 0:Disabled. DEFAULT: Backgroud update is enabled. Runtime setting is allowed. + MAAT_OPT_ACCEPT_TAGS, //VALUE is a const char*, MUST end with '\0', SIZE= strlen(string+'\0')+1. Format is a JSON, e.g.{"tags":[{"tag":"location","value":"Beijing/ChaoYang/Huayan/22A"},{"tag":"isp","value":"telecom"}]} + MAAT_OPT_FOREIGN_CONT_DIR, //VALUE is a const char*, MUST end with '\0', SIZE= strlen(string+'\0')+1. Specifies a local diretory to store foreign content. Default: []table_info_path]_files + MAAT_OPT_FOREIGN_CONT_LINGER //VALUE is interger *, SIZE=sizeof(int). Greater than 0: delete after VALUE seconds; 0: delete foreign content right after the notification callbacks; Less than 0: NEVER delete. Default: 0. + }; //return -1 if failed, return 0 on success; int Maat_set_feather_opt(Maat_feather_t feather,enum MAAT_INIT_OPT type,const void* value,int size); enum MAAT_STATE_OPT { - MAAT_STATE_VERSION=1, //Get current maat version. VALUE is long long, SIZE=sizeof(long long). + MAAT_STATE_VERSION=1, //Get current maat version. VALUE is long long, SIZE=sizeof(long long). MAAT_STATE_LAST_UPDATING_TABLE //Query at Maat_finish_callback_t to determine whether this table is the last one to update. VALUE is interger, SIZE=sizeof(int), 1:yes, 0: no }; int Maat_read_state(Maat_feather_t feather, enum MAAT_STATE_OPT type, void* value, int size); @@ -237,7 +240,7 @@ int Maat_similar_scan_string(Maat_feather_t feather,int table_id void Maat_clean_status(scan_status_t* mid); enum MAAT_RULE_OPT { - MAAT_RULE_SERV_DEFINE //VALUE is a char* buffer,SIZE= buffer size. + MAAT_RULE_SERV_DEFINE //VALUE is a char* buffer,SIZE= buffer size. }; int Maat_read_rule(Maat_feather_t feather, const struct Maat_rule_t* rule, enum MAAT_RULE_OPT type, void* value, int size); #endif // H_MAAT_RULE_H_INCLUDE diff --git a/src/inc/moodycamel_maat_rule.h b/src/inc/moodycamel_maat_rule.h index bc8ea8c..3251e09 100644 --- a/src/inc/moodycamel_maat_rule.h +++ b/src/inc/moodycamel_maat_rule.h @@ -17,10 +17,10 @@ enum MAAT_INIT_OPT MAAT_OPT_FULL_CFG_DIR, //VALUE is a const char*, MUST end with '\0', SIZE= strlen(string+'\0')+1.DEFAULT: no default. MAAT_OPT_INC_CFG_DIR, //VALUE is a const char*, MUST end with '\0', SIZE= strlen(string+'\0')+1.DEFAULT: no default. MAAT_OPT_JSON_FILE_PATH, //VALUE is a const char*, MUST end with '\0', SIZE= strlen(string+'\0')+1.DEFAULT: no default. - MAAT_OPT_STAT_ON, //VALUE is NULL,SIZE is 0. MAAT_OPT_STAT_FILE_PATH must be set. Default: stat OFF. - MAAT_OPT_PERF_ON, //VALUE is NULL,SIZE is 0. MAAT_OPT_STAT_FILE_PATH must be set. Default: stat OFF. + MAAT_OPT_STAT_ON, //VALUE is NULL, SIZE is 0. MAAT_OPT_STAT_FILE_PATH must be set. Default: stat OFF. + MAAT_OPT_PERF_ON, //VALUE is NULL, SIZE is 0. MAAT_OPT_STAT_FILE_PATH must be set. Default: stat OFF. MAAT_OPT_STAT_FILE_PATH, //VALUE is a const char*, MUST end with '\0', SIZE= strlen(string+'\0')+1. DEFAULT: no default. - MAAT_OPT_SCAN_DETAIL, //VALUE is interger *, SIZE=sizeof(int). 0: not return any detail;1: return hit pos, not include regex grouping; + MAAT_OPT_SCAN_DETAIL, //VALUE is interger *, SIZE=sizeof(int). 0: not return any detail;1: return hit pos, not include regex grouping. // 2 return hit pos and regex grouping pos;DEFAULT:0 MAAT_OPT_INSTANCE_NAME, //VALUE is a const char*, MUST end with '\0', SIZE= strlen(string+'\0')+1, no more than 11 bytes.DEFAULT: MAAT_$tableinfo_path$. MAAT_OPT_DECRYPT_KEY, //VALUE is a const char*, MUST end with '\0', SIZE= strlen(string+'\0')+1. No DEFAULT. @@ -32,8 +32,11 @@ enum MAAT_INIT_OPT MAAT_OPT_CUMULATIVE_UPDATE_OFF, //VALUE is NULL,SIZE is 0. Default: CUMMULATIVE UPDATE ON. MAAT_OPT_LOAD_VERSION_FROM, //VALUE is a long long, SIZE=sizeof(long long). Default: Load the Latest. Only valid in redis mode, and maybe failed for too old. //This option also disables background update. - MAAT_OPT_ENABLE_UPDATE //VALUE is interger, SIZE=sizeof(int). 1: Enabled, 0:Disabled. DEFAULT: Backgroud update is enabled. Runtime setting is allowed. -}; + MAAT_OPT_ENABLE_UPDATE, //VALUE is interger, SIZE=sizeof(int). 1: Enabled, 0:Disabled. DEFAULT: Backgroud update is enabled. Runtime setting is allowed. + MAAT_OPT_ACCEPT_TAGS, //VALUE is a const char*, MUST end with '\0', SIZE= strlen(string+'\0')+1. Format is a JSON, e.g.{"tags":[{"tag":"location","value":"Beijing/ChaoYang/Huayan/22A"},{"tag":"isp","value":"telecom"}]} + MAAT_OPT_FOREIGN_CONT_DIR, //VALUE is a const char*, MUST end with '\0', SIZE= strlen(string+'\0')+1. Specifies a local diretory to store foreign content. Default: []table_info_path]_files + MAAT_OPT_FOREIGN_CONT_LINGER //VALUE is interger *, SIZE=sizeof(int). Greater than 0: delete after VALUE seconds; 0: delete foreign content right after the notification callbacks; Less than 0: NEVER delete. Default: 0. + }; enum MAAT_STATE_OPT { diff --git a/src/lib/libMESA_htable.so b/src/lib/libMESA_htable.so deleted file mode 100644 index ff7f10b029a16caec1377b0fc6a9bb9fa20712a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 155176 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 diff --git a/src/rt/rt.mk b/src/rt/rt.mk index 5a4a16d..36480c8 100644 --- a/src/rt/rt.mk +++ b/src/rt/rt.mk @@ -11,6 +11,7 @@ OBJS_$(d) :=\ $(OBJ_DIR)/rt_file.o\ $(OBJ_DIR)/rt_stdlib.o\ $(OBJ_DIR)/rt_string.o\ + $(OBJ_DIR)/rt_tmr.o\ $(OBJ_DIR)/rt_time.o CFLAGS_LOCAL += -I$(d) diff --git a/src/rt/rt_tmr.c b/src/rt/rt_tmr.c new file mode 100644 index 0000000..e47a5e2 --- /dev/null +++ b/src/rt/rt_tmr.c @@ -0,0 +1,212 @@ +/************************************************************************* + > File Name: rt_tmr.c + > Author: + > Mail: + > Created Time: 2018å¹´09月19æ—¥ 星期三 15æ—¶57分58ç§’ + ************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rt_stdlib.h" +#include "rt_sync.h" +#include "rt_string.h" +#include "rt_common.h" + +static int init; + +enum { + ALLOWED, + FORBIDDEN +}; + +struct tmr_task{ + rt_pthread pid; + + rt_pthread_attr *attr; + + void * (*routine)(void *); +}; + +struct timer_t{ + +#define TMR_INVALID -1 + + int module; + + /** unique id */ + uint32_t uid; + + /** Make sure that allocate desc member with malloc like function. */ + char *desc; + + /** in ms */ + int64_t interval; + + int64_t curr_ticks; + + enum {TMR_PERIODIC, TMR_ASHOT} type; + + enum {TMR_STOPPED, TMR_STARTED} status; + + void (*routine)(uint32_t uid, int argc, char **argv); + + int argc; + + char **argv; + + int recycle; +}; + +struct timer_t *_this; + +static volatile atomic64_t g_sys_cur_ticks = ATOMIC_INIT(0); + +struct timer_t * +tmr_set(uint32_t uid, + void (*proc)(struct timer_t *t)) +{ + uid = uid; + + if(likely(proc)) + proc(_this); + + return _this; +} + +static void tmr_enable(struct timer_t *t) +{ + t->status = TMR_STARTED; +} + +static void tmr_disable(struct timer_t *t) +{ + t->status = TMR_STOPPED; +} + +void tmr_start(uint32_t uid) +{ + tmr_set(uid, tmr_enable); +} + +void tmr_stop(uint32_t uid) +{ + tmr_set(uid, tmr_disable); +} + +static void tmr_handler() +{ + int64_t old_ticks = 0; + + old_ticks = atomic64_add(&g_sys_cur_ticks, 1); + if (likely(_this->status == TMR_STARTED)){ + if (likely(old_ticks >= _this->curr_ticks)) { + _this->routine(_this->uid, _this->argc, _this->argv); + if (likely(TMR_ASHOT == _this->type)) + _this->status= TMR_STOPPED; + else + _this->curr_ticks = old_ticks + _this->interval; + } + } +} + +static void realtimer_init() +{ + static struct itimerval tmr; + + signal(SIGALRM, (void *) tmr_handler); + tmr.it_interval.tv_sec = 1; + tmr.it_interval.tv_usec = 0; + tmr.it_value.tv_sec = 1; + tmr.it_value.tv_usec = 0; + setitimer(ITIMER_REAL, &tmr, NULL); + +} + +static void +tmr_default_routine(uint32_t uid, int __attribute__((__unused__))argc, + char __attribute__((__unused__))**argv) +{ + static int count[2] = {0}; + + count[uid%2] ++; + printf ("default timer [%u, %d] routine has occured\n", uid, count[uid%2]); +} + +static __rt_always_inline__ void * +tmr_alloc() +{ + struct timer_t *t; + + t = kmalloc(sizeof(struct timer_t), MPF_CLR, -1); + if(likely(t)){ + t->uid = TMR_INVALID; + t->routine = tmr_default_routine; + t->interval = 3; //tmr_internal_trans(3); + t->recycle = ALLOWED; + t->type = TMR_ASHOT; + t->status = TMR_STOPPED; + } + return t; +} + +static void * +tmr_daemon(void __attribute__((__unused__))*pv_par ) +{ + FOREVER{ + sleep(86400); + } + return NULL; +} + +uint32_t tmr_create(int module, + const char *desc, + void (*routine)(uint32_t, int, char **), int argc, char **argv, int sec) +{ + uint32_t uid = -1; + + if(unlikely(!desc)){ + goto finish; + } + + _this = (struct timer_t *)tmr_alloc(); + if(unlikely(!_this)){ + goto finish; + } + + _this->module = module; + _this->desc = strdup(desc); + _this->curr_ticks = 0; + _this->interval = sec; + _this->routine = routine ? routine : tmr_default_routine; + _this->type = TMR_PERIODIC; + _this->uid = uid = 1; + _this->recycle = ALLOWED; + _this->status = TMR_STOPPED; + _this->argc = argc; + _this->argv = argv; + + if(likely(init == 0)){ + struct tmr_task *task = (struct tmr_task*)kmalloc(sizeof(struct tmr_task), MPF_CLR, -1); + if (likely(task)){ + if (pthread_create(&task->pid, task->attr, tmr_daemon, NULL)){ + goto finish; + } + if (pthread_detach(task->pid)){ + goto finish; + } + } + init = 1; + realtimer_init(); + } + +finish: + return uid; +} + diff --git a/src/rt/rt_tmr.h b/src/rt/rt_tmr.h new file mode 100644 index 0000000..27318bd --- /dev/null +++ b/src/rt/rt_tmr.h @@ -0,0 +1,25 @@ +/************************************************************************* + > File Name: rt_tmr.h + > Author: + > Mail: + > Created Time: 2018å¹´09月19æ—¥ 星期三 15æ—¶58分04ç§’ + ************************************************************************/ + +#ifndef _RT_TMR_H +#define _RT_TMR_H + +//#define RT_TMR_ADVANCED + +extern void tmr_start(uint32_t uid); +extern void tmr_stop(uint32_t uid); + +/** +* routine: must be a reentrant function. +* An unknown error ocurrs if a thread-safe function called as a routione. +*/ +extern uint32_t tmr_create(int module, + const char *desc, + void (*routine)(uint32_t, int, char **), int argc, char **argv, int sec); + +#endif +