diff --git a/CHANGELOG.md b/CHANGELOG.md index 17b6459c..3c0591f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # 版本发布日志 +## 2.2408.4-20240823 + +- 优化 网元端文件目录/tmp/omc 上传push 下载pull +- 新增 网元文件操作可删除本地临时文件 +- 新增 系统菜单-信令抓包文件 +- 新增 KPI添加CBC +- 更新 CBC参数配置可选属性 + ## 2.2408.3-20240816 - 新增 中间件-接口加解密,配合前端CryptoJS加解密 diff --git a/config/goldkpi/核心网网元黄金指标集.xlsx b/config/goldkpi/核心网网元黄金指标集.xlsx index 0b4a97c4..dde44495 100644 Binary files a/config/goldkpi/核心网网元黄金指标集.xlsx and b/config/goldkpi/核心网网元黄金指标集.xlsx differ diff --git a/config/param/cbc_param_config.yaml b/config/param/cbc_param_config.yaml new file mode 100644 index 00000000..4da1094c --- /dev/null +++ b/config/param/cbc_param_config.yaml @@ -0,0 +1,120 @@ +cbc: + system: + display: "System Config" + sort: 1 + list: + - name: "name" + type: "string" + value: "CBC" + access: "read-write" + filter: "0~64" + display: "CBC Name" + comment: "" + - name: "instance" + type: "string" + value: "CBC-001" + access: "read-write" + filter: "0~64" + display: "NF Instance" + comment: "" + - name: "sbiIp" + type: "string" + value: "127.0.0.1" + access: "read-write" + filter: "0~64" + display: "SBI Server IP" + comment: "" + - name: "sbiPort" + type: "int" + value: "9090" + access: "read-write" + filter: "0~65535" + display: "SBI Server Port" + comment: "0~65535" + - name: "sbiScheme" + type: "enum" + value: "0" + access: "read-write" + filter: '{"0":"http","1":"https"}' + display: "SBI Scheme" + comment: "http or https" + + amfProfile: + display: "AMF Profile" + sort: 3 + array: + - name: "index" + type: "int" + value: "0" + access: "read-only" + filter: "0~15" + display: "Index" + comment: "0~15" + - name: "name" + type: "string" + value: "AMF" + access: "read-write" + filter: "0~64" + display: "NF Name" + comment: "" + - name: "uri" + type: "string" + value: "http://172.16.5.130:8080" + access: "read-write" + filter: "0~64" + display: "AMF URI" + comment: "" + - name: "plmnId" + type: "regex" + value: "00101" + access: "read-write" + filter: "^[0-9]{5,6}$" + display: "PLMN ID" + comment: "" + - name: "tac" + type: "string" + value: "1" + access: "read-write" + filter: "0~8" + display: "TAC" + comment: "0~16777215" + + mmeProfile: + display: "MME Profile" + sort: 5 + array: + - name: "index" + type: "int" + value: "0" + access: "read-only" + filter: "0~15" + display: "Index" + comment: "0~15" + - name: "name" + type: "string" + value: "MME" + access: "read-write" + filter: "0~64" + display: "NF Name" + comment: "" + - name: "uri" + type: "string" + value: "sctp://192.168.1.1:9090" + access: "read-write" + filter: "0~64" + display: "MME URI" + comment: "" + - name: "plmnId" + type: "regex" + value: "00101" + access: "read-write" + filter: "^[0-9]{5,6}$" + display: "PLMN ID" + comment: "" + - name: "tac" + type: "string" + value: "1" + access: "read-write" + filter: "0~8" + display: "TAC" + comment: "0~16777215" diff --git a/database/common/kpi_title.sql b/database/common/kpi_title.sql index a000bbe5..5628f9c7 100644 --- a/database/common/kpi_title.sql +++ b/database/common/kpi_title.sql @@ -287,5 +287,10 @@ INSERT INTO `kpi_title` VALUES (248, 'SMSC', 'SMSC.A.29', '{\"cn\": \"SMSC到SMP INSERT INTO `kpi_title` VALUES (249, 'SMSC', 'SMSC.A.30', '{\"cn\": \"SMSC到SMPP短信成功次数\", \"en\": \"SMSC.SMSCtoSMPPucc\"}', 'SMSC到SMPP短信成功次数', 'SMSC.SMSCtoSMPPucc'); INSERT INTO `kpi_title` VALUES (250, 'SMSC', 'SMSC.A.31', '{\"cn\": \"SMSC到SMPP短信失败次数\", \"en\": \"SMSC.SMSCtoSMPPFail\"}', 'SMSC到SMPP短信失败次数', 'SMSC.SMSCtoSMPPFail'); INSERT INTO `kpi_title` VALUES (251, 'SMSC', 'SMSC.A.32', '{\"cn\": \"SMSC到SMPP短信字节数\", \"en\": \"SMSC.SMSCtoSMPPBytes\"}', 'SMSC到SMPP短信字节数', 'SMSC.SMSCtoSMPPBytes'); +-- 更新CBC 20240823 +INSERT INTO `kpi_title` VALUES (252, 'CBC', 'CBC.A.01', '{\"cn\": \"CBC 发起 Create Write 消息成功\", \"en\": \"WarningReqSucc\"}', 'CBC 发起 Create Write 消息成功', 'WarningReqSucc'); +INSERT INTO `kpi_title` VALUES (253, 'CBC', 'CBC.A.02', '{\"cn\": \"CBC 发起 Create Write 消息失败\", \"en\": \"WarningReqFail\"}', 'CBC 发起 Create Write 消息失败', 'WarningReqFail'); +INSERT INTO `kpi_title` VALUES (254, 'CBC', 'CBC.A.03', '{\"cn\": \"CBC 发起 Cancel Write 消息成功\", \"en\": \"WarningCancelSucc\"}', 'CBC 发起 Cancel Write 消息成功', 'WarningCancelSucc'); +INSERT INTO `kpi_title` VALUES (255, 'CBC', 'CBC.A.04', '{\"cn\": \"CBC 发起 Cancel Write 消息失败\", \"en\": \"WarningCancelFail\"}', 'CBC 发起 Cancel Write 消息失败', 'WarningCancelFail'); SET FOREIGN_KEY_CHECKS = 1; diff --git a/database/common/ne_config.sql b/database/common/ne_config.sql index af4241c7..298b49d9 100644 --- a/database/common/ne_config.sql +++ b/database/common/ne_config.sql @@ -106,4 +106,9 @@ INSERT INTO `ne_config` VALUES (77, 'UPF', 'dpiCommon', 'DPI Common', 'list', '[ INSERT INTO `ne_config` VALUES (78, 'UPF', 'dpiHeaderEnrichInfoList', 'DPI Header Enrich Info List', 'array', '[{\"access\":\"read-only\",\"comment\":\"1~32\",\"display\":\"Index\",\"filter\":\"1~32\",\"name\":\"index\",\"type\":\"int\",\"value\":\"1\"},{\"access\":\"read-write\",\"comment\":\"\",\"display\":\"Type\",\"filter\":\"\",\"name\":\"type\",\"type\":\"int\",\"value\":\"0\"},{\"access\":\"read-write\",\"comment\":\"\",\"display\":\"Name\",\"filter\":\"\",\"name\":\"name\",\"type\":\"string\",\"value\":\"\"},{\"access\":\"read-write\",\"comment\":\"\",\"display\":\"Value\",\"filter\":\"\",\"name\":\"value\",\"type\":\"string\",\"value\":\"\"}]', 31, '', 1721705774501); INSERT INTO `ne_config` VALUES (79, 'UPF', 'dpiAppList', 'DPI APP List', 'array', '[{\"access\":\"read-write\",\"comment\":\"1~32\",\"display\":\"Index\",\"filter\":\"1~32\",\"name\":\"index\",\"type\":\"int\",\"value\":\"1\"},{\"access\":\"read-write\",\"comment\":\"\",\"display\":\"APP Name\",\"filter\":\"\",\"name\":\"appName\",\"type\":\"string\",\"value\":\"\"},{\"access\":\"read-write\",\"comment\":\"\",\"display\":\"Proxy Enabled\",\"filter\":\"0~1\",\"name\":\"proxyEnabled\",\"type\":\"int\",\"value\":\"0\"},{\"access\":\"read-write\",\"comment\":\"\",\"display\":\"Force Check Type\",\"filter\":\"0~1\",\"name\":\"forceCheckType\",\"type\":\"int\",\"value\":\"0\"},{\"access\":\"read-write\",\"comment\":\"\",\"display\":\"N3 Interface ID\",\"filter\":\"0~32\",\"name\":\"n3InterfaceId\",\"type\":\"int\",\"value\":\"0\"},{\"access\":\"read-write\",\"comment\":\"\",\"display\":\"N6 Interface ID\",\"filter\":\"0~32\",\"name\":\"n6InterfaceId\",\"type\":\"int\",\"value\":\"0\"},{\"access\":\"read-write\",\"array\":[{\"access\":\"read-only\",\"comment\":\"1~32\",\"display\":\"Index\",\"filter\":\"1~32\",\"name\":\"index\",\"type\":\"int\",\"value\":\"1\"},{\"access\":\"read-write\",\"comment\":\"\",\"display\":\"Rule ID\",\"filter\":\"\",\"name\":\"ruleId\",\"type\":\"int\",\"value\":\"\"},{\"access\":\"read-write\",\"comment\":\"\",\"display\":\"REGEX Match\",\"filter\":\"\",\"name\":\"regexMatch\",\"type\":\"string\",\"value\":\"\"},{\"access\":\"read-write\",\"comment\":\"\",\"display\":\"Flow Description\",\"filter\":\"\",\"name\":\"flowDescription\",\"type\":\"string\",\"value\":\"\"},{\"access\":\"read-write\",\"comment\":\"\",\"display\":\"Custom Name\",\"filter\":\"\",\"name\":\"customName\",\"type\":\"string\",\"value\":\"\"}],\"comment\":\"\",\"display\":\"Rule List\",\"filter\":\"0~32\",\"name\":\"ruleList\",\"type\":\"int\",\"value\":\"1\"}]', 33, '', 1721705774505); +-- 更新 CBC 配置 20240823 +INSERT INTO `ne_config` VALUES (80, 'CBC', 'system', 'System Config', 'list', '[{\"access\":\"read-write\",\"comment\":\"\",\"display\":\"CBC Name\",\"filter\":\"0~64\",\"name\":\"name\",\"type\":\"string\",\"value\":\"CBC\"},{\"access\":\"read-write\",\"comment\":\"\",\"display\":\"NF Instance\",\"filter\":\"0~64\",\"name\":\"instance\",\"type\":\"string\",\"value\":\"CBC-001\"},{\"access\":\"read-write\",\"comment\":\"\",\"display\":\"SBI Server IP\",\"filter\":\"0~64\",\"name\":\"sbiIp\",\"type\":\"string\",\"value\":\"127.0.0.1\"},{\"access\":\"read-write\",\"comment\":\"0~65535\",\"display\":\"SBI Server Port\",\"filter\":\"0~65535\",\"name\":\"sbiPort\",\"type\":\"int\",\"value\":\"9090\"},{\"access\":\"read-write\",\"comment\":\"http or https\",\"display\":\"SBI Scheme\",\"filter\":\"{\\\"0\\\":\\\"http\\\",\\\"1\\\":\\\"https\\\"}\",\"name\":\"sbiScheme\",\"type\":\"enum\",\"value\":\"0\"}]', 1, '', 1724327154483); +INSERT INTO `ne_config` VALUES (81, 'CBC', 'amfProfile', 'AMF Profile', 'array', '[{\"access\":\"read-only\",\"comment\":\"0~15\",\"display\":\"Index\",\"filter\":\"0~15\",\"name\":\"index\",\"type\":\"int\",\"value\":\"0\"},{\"access\":\"read-write\",\"comment\":\"\",\"display\":\"NF Name\",\"filter\":\"0~64\",\"name\":\"name\",\"type\":\"string\",\"value\":\"AMF\"},{\"access\":\"read-write\",\"comment\":\"\",\"display\":\"AMF URI\",\"filter\":\"0~64\",\"name\":\"uri\",\"type\":\"string\",\"value\":\"http://172.16.5.130:8080\"},{\"access\":\"read-write\",\"comment\":\"\",\"display\":\"PLMN ID\",\"filter\":\"^[0-9]{5,6}$\",\"name\":\"plmnId\",\"type\":\"regex\",\"value\":\"00101\"},{\"access\":\"read-write\",\"comment\":\"0~16777215\",\"display\":\"TAC\",\"filter\":\"0~8\",\"name\":\"tac\",\"type\":\"string\",\"value\":\"1\"}]', 3, '', 1724327154499); +INSERT INTO `ne_config` VALUES (82, 'CBC', 'mmeProfile', 'MME Profile', 'array', '[{\"access\":\"read-only\",\"comment\":\"0~15\",\"display\":\"Index\",\"filter\":\"0~15\",\"name\":\"index\",\"type\":\"int\",\"value\":\"0\"},{\"access\":\"read-write\",\"comment\":\"\",\"display\":\"NF Name\",\"filter\":\"0~64\",\"name\":\"name\",\"type\":\"string\",\"value\":\"MME\"},{\"access\":\"read-write\",\"comment\":\"\",\"display\":\"MME URI\",\"filter\":\"0~64\",\"name\":\"uri\",\"type\":\"string\",\"value\":\"sctp://192.168.1.1:9090\"},{\"access\":\"read-write\",\"comment\":\"\",\"display\":\"PLMN ID\",\"filter\":\"^[0-9]{5,6}$\",\"name\":\"plmnId\",\"type\":\"regex\",\"value\":\"00101\"},{\"access\":\"read-write\",\"comment\":\"0~16777215\",\"display\":\"TAC\",\"filter\":\"0~8\",\"name\":\"tac\",\"type\":\"string\",\"value\":\"1\"}]', 5, '', 1724327154504); + SET FOREIGN_KEY_CHECKS=1; diff --git a/database/install/sys_dict_data1_i18n_zh.sql b/database/install/sys_dict_data1_i18n_zh.sql index 8c9ea585..0b70ecde 100644 --- a/database/install/sys_dict_data1_i18n_zh.sql +++ b/database/install/sys_dict_data1_i18n_zh.sql @@ -655,7 +655,7 @@ INSERT INTO `sys_dict_data` VALUES (2144, 2144, 'log.operate.title.mmeUE', '4G INSERT INTO `sys_dict_data` VALUES (2145, 2145, 'menu.system.user.editPost', '修改用户岗位', 'i18n_zh', '', '', '1', 'supervisor', 1705550000000, '', 0, ''); INSERT INTO `sys_dict_data` VALUES (2146, 2146, 'menu.dashboard.smscCDR', '短信话单', 'i18n_zh', '', '', '1', 'supervisor', 1717051745866, '', 0, ''); INSERT INTO `sys_dict_data` VALUES (2147, 2147, 'log.operate.title.smscCDR', '短信话单', 'i18n_zh', '', '', '1', 'supervisor', 1705550000000, '', 0, ''); --- INSERT INTO `sys_dict_data` VALUES (2148, 2148, 'dictData.udm_sub_cn_type.1', '5G', 'i18n_zh', '', '', '1', 'supervisor', 1705550000000, '', 0, ''); +INSERT INTO `sys_dict_data` VALUES (2148, 2148, 'menu.trace.pcapFile', '信令抓包文件', 'i18n_zh', '', '', '1', 'supervisor', 1705550000000, '', 0, ''); -- INSERT INTO `sys_dict_data` VALUES (2149, 2149, 'dictData.udm_sub_cn_type.2', '4G', 'i18n_zh', '', '', '1', 'supervisor', 1705550000000, '', 0, ''); -- INSERT INTO `sys_dict_data` VALUES (2150, 2150, 'dictData.udm_sub_cn_type.3', '5G&4G', 'i18n_zh', '', '', '1', 'supervisor', 1705550000000, '', 0, ''); INSERT INTO `sys_dict_data` VALUES (2151, 2151, 'menu.system.setting.doc', '系统使用文档', 'i18n_zh', '', '', '1', 'supervisor', 1705550000000, '', 0, ''); diff --git a/database/install/sys_dict_data2_i18n_en.sql b/database/install/sys_dict_data2_i18n_en.sql index 04e83267..9af59384 100644 --- a/database/install/sys_dict_data2_i18n_en.sql +++ b/database/install/sys_dict_data2_i18n_en.sql @@ -655,7 +655,7 @@ INSERT INTO `sys_dict_data` VALUES (4144, 4144, 'log.operate.title.mmeUE', '4G U INSERT INTO `sys_dict_data` VALUES (4145, 4145, 'menu.system.user.editPost', 'Modify User Post', 'i18n_en', '', '', '1', 'supervisor', 1718441035866, '', 0, ''); INSERT INTO `sys_dict_data` VALUES (4146, 4146, 'menu.dashboard.smscCDR', 'SMS CDR', 'i18n_en', '', '', '1', 'supervisor', 1705550000000, '', 0, ''); INSERT INTO `sys_dict_data` VALUES (4147, 4147, 'log.operate.title.smscCDR', 'SMS CDR', 'i18n_en', '', '', '1', 'supervisor', 1705550000000, '', 0, ''); --- INSERT INTO `sys_dict_data` VALUES (4148, 4148, 'dictData.udm_sub_cn_type.1', '5G', 'i18n_en', '', '', '1', 'supervisor', 1718441035866, '', 0, ''); +INSERT INTO `sys_dict_data` VALUES (4148, 4148, 'menu.trace.pcapFile', 'Signaling Capture File', 'i18n_en', '', '', '1', 'supervisor', 1718441035866, '', 0, ''); -- INSERT INTO `sys_dict_data` VALUES (4149, 4149, 'dictData.udm_sub_cn_type.2', '4G', 'i18n_en', '', '', '1', 'supervisor', 1718441035866, '', 0, ''); -- INSERT INTO `sys_dict_data` VALUES (4150, 4150, 'dictData.udm_sub_cn_type.3', '5G&4G', 'i18n_en', '', '', '1', 'supervisor', 1718441035866, '', 0, ''); INSERT INTO `sys_dict_data` VALUES (4151, 4151, 'menu.system.setting.doc', 'System User Documentation', 'i18n_en', '', '', '1', 'supervisor', 1705550000000, '', 0, ''); diff --git a/database/install/sys_menu.sql b/database/install/sys_menu.sql index 2cff7f26..f7db8d58 100644 --- a/database/install/sys_menu.sql +++ b/database/install/sys_menu.sql @@ -196,6 +196,7 @@ INSERT INTO `sys_menu` VALUES (2154, 'menu.ne.neConfigBackup', 4, 29, 'neConfigB INSERT INTO `sys_menu` VALUES (2155, 'menu.common.delete', 2154, 1, '#', '', '1', '1', 'B', '1', '1', 'ne:neConfigBackup:remove', '#', 'supervisor', 1721902269805, '', 0, ''); INSERT INTO `sys_menu` VALUES (2156, 'menu.common.edit', 2154, 2, '#', '', '1', '1', 'B', '1', '1', 'ne:neConfigBackup:edit', '#', 'supervisor', 1721902269805, '', 0, ''); INSERT INTO `sys_menu` VALUES (2157, 'menu.dashboard.smscCDR', 2140, 9, 'smscCDR', 'dashboard/smscCDR/index', '1', '0', 'M', '1', '1', 'dashboard:cdr:index', 'icon-paixu', 'supervisor', 1723107637982, 'supervisor', 1723107637982, ''); +INSERT INTO `sys_menu` VALUES (2158, 'menu.trace.pcapFile', 2083, 4, 'pcap/inline/file', 'traceManage/pcap/file', '1', '1', 'M', '0', '1', 'traceManage:pcap:index', '#', 'supervisor', 1724144595914, '', 0, ''); -- tenant management for admin user INSERT INTO `sys_menu` VALUES (10000, 'menu.security.tenant', 2113, 5, 'tenant', 'system/tenant/index', '1', '1', 'M', '1', '1', 'system:tenant:list', 'icon-yuzhanghao1', 'supervisor', 1700000000000, NULL, 0, 'menu.security.tenantRemark'); diff --git a/database/install/sys_role_menu.sql b/database/install/sys_role_menu.sql index c8a735ec..2776938e 100644 --- a/database/install/sys_role_menu.sql +++ b/database/install/sys_role_menu.sql @@ -159,6 +159,7 @@ INSERT IGNORE INTO `sys_role_menu` VALUES (2, 2154); INSERT IGNORE INTO `sys_role_menu` VALUES (2, 2155); INSERT IGNORE INTO `sys_role_menu` VALUES (2, 2156); INSERT IGNORE INTO `sys_role_menu` VALUES (2, 2157); +INSERT IGNORE INTO `sys_role_menu` VALUES (2, 2158); INSERT IGNORE INTO `sys_role_menu` VALUES (3, 1); INSERT IGNORE INTO `sys_role_menu` VALUES (3, 4); INSERT IGNORE INTO `sys_role_menu` VALUES (3, 5); diff --git a/database/upgrade/upg_sys_dict_data1_i18n_zh.sql b/database/upgrade/upg_sys_dict_data1_i18n_zh.sql index b80beed3..ff062f2e 100644 --- a/database/upgrade/upg_sys_dict_data1_i18n_zh.sql +++ b/database/upgrade/upg_sys_dict_data1_i18n_zh.sql @@ -662,7 +662,7 @@ REPLACE INTO `sys_dict_data` VALUES (2144, 2144, 'log.operate.title.mmeUE', '4G REPLACE INTO `sys_dict_data` VALUES (2145, 2145, 'menu.system.user.editPost', '修改用户岗位', 'i18n_zh', '', '', '1', 'supervisor', 1705550000000, '', 0, ''); REPLACE INTO `sys_dict_data` VALUES (2146, 2146, 'menu.dashboard.smscCDR', '短信话单', 'i18n_zh', '', '', '1', 'supervisor', 1717051745866, '', 0, ''); REPLACE INTO `sys_dict_data` VALUES (2147, 2147, 'log.operate.title.smscCDR', '短信话单', 'i18n_zh', '', '', '1', 'supervisor', 1705550000000, '', 0, ''); --- REPLACE INTO `sys_dict_data` VALUES (2148, 2148, 'dictData.udm_sub_cn_type.1', '5G', 'i18n_zh', '', '', '1', 'supervisor', 1705550000000, '', 0, ''); +REPLACE INTO `sys_dict_data` VALUES (2148, 2148, 'menu.trace.pcapFile', '信令抓包文件', 'i18n_zh', '', '', '1', 'supervisor', 1705550000000, '', 0, ''); -- REPLACE INTO `sys_dict_data` VALUES (2149, 2149, 'dictData.udm_sub_cn_type.2', '4G', 'i18n_zh', '', '', '1', 'supervisor', 1705550000000, '', 0, ''); -- REPLACE INTO `sys_dict_data` VALUES (2150, 2150, 'dictData.udm_sub_cn_type.3', '5G&4G', 'i18n_zh', '', '', '1', 'supervisor', 1705550000000, '', 0, ''); REPLACE INTO `sys_dict_data` VALUES (2151, 2151, 'menu.system.setting.doc', '系统使用文档', 'i18n_zh', '', '', '1', 'supervisor', 1705550000000, '', 0, ''); diff --git a/database/upgrade/upg_sys_dict_data2_i18n_en.sql b/database/upgrade/upg_sys_dict_data2_i18n_en.sql index 02db1429..6bb9a2c8 100644 --- a/database/upgrade/upg_sys_dict_data2_i18n_en.sql +++ b/database/upgrade/upg_sys_dict_data2_i18n_en.sql @@ -657,7 +657,7 @@ REPLACE INTO `sys_dict_data` VALUES (4144, 4144, 'log.operate.title.mmeUE', '4G REPLACE INTO `sys_dict_data` VALUES (4145, 4145, 'menu.system.user.editPost', 'Modify User Post', 'i18n_en', '', '', '1', 'supervisor', 1718441035866, '', 0, ''); REPLACE INTO `sys_dict_data` VALUES (4146, 4146, 'menu.dashboard.smscCDR', 'SMS CDR', 'i18n_en', '', '', '1', 'supervisor', 1705550000000, '', 0, ''); REPLACE INTO `sys_dict_data` VALUES (4147, 4147, 'log.operate.title.smscCDR', 'SMS CDR', 'i18n_en', '', '', '1', 'supervisor', 1705550000000, '', 0, ''); --- REPLACE INTO `sys_dict_data` VALUES (4148, 4148, 'dictData.udm_sub_cn_type.1', '5G', 'i18n_en', '', '', '1', 'supervisor', 1718441035866, '', 0, ''); +REPLACE INTO `sys_dict_data` VALUES (4148, 4148, 'menu.trace.pcapFile', 'Signaling Capture File', 'i18n_en', '', '', '1', 'supervisor', 1718441035866, '', 0, ''); -- REPLACE INTO `sys_dict_data` VALUES (4149, 4149, 'dictData.udm_sub_cn_type.2', '4G', 'i18n_en', '', '', '1', 'supervisor', 1718441035866, '', 0, ''); -- REPLACE INTO `sys_dict_data` VALUES (4150, 4150, 'dictData.udm_sub_cn_type.3', '5G&4G', 'i18n_en', '', '', '1', 'supervisor', 1718441035866, '', 0, ''); REPLACE INTO `sys_dict_data` VALUES (4151, 4151, 'menu.system.setting.doc', 'System User Documentation', 'i18n_en', '', '', '1', 'supervisor', 1705550000000, '', 0, ''); diff --git a/database/upgrade/upg_sys_menu.sql b/database/upgrade/upg_sys_menu.sql index 0580be55..9933464f 100644 --- a/database/upgrade/upg_sys_menu.sql +++ b/database/upgrade/upg_sys_menu.sql @@ -179,6 +179,7 @@ REPLACE INTO `sys_menu` VALUES (2154, 'menu.ne.neConfigBackup', 4, 29, 'neConfig REPLACE INTO `sys_menu` VALUES (2155, 'menu.common.delete', 2154, 1, '#', '', '1', '1', 'B', '1', '1', 'ne:neConfigBackup:remove', '#', 'supervisor', 1721902269805, '', 0, ''); REPLACE INTO `sys_menu` VALUES (2156, 'menu.common.edit', 2154, 2, '#', '', '1', '1', 'B', '1', '1', 'ne:neConfigBackup:edit', '#', 'supervisor', 1721902269805, '', 0, ''); REPLACE INTO `sys_menu` VALUES (2157, 'menu.dashboard.smscCDR', 2140, 9, 'smscCDR', 'dashboard/smscCDR/index', '1', '0', 'M', '1', '1', 'dashboard:cdr:index', 'icon-paixu', 'supervisor', 1723107637982, 'supervisor', 1723107637982, ''); +REPLACE INTO `sys_menu` VALUES (2158, 'menu.trace.pcapFile', 2083, 4, 'pcap/inline/file', 'traceManage/pcap/file', '1', '1', 'M', '0', '1', 'traceManage:pcap:index', '#', 'supervisor', 1724144595914, '', 0, ''); -- multi-tenancy diff --git a/database/upgrade/upg_sys_role_menu.sql b/database/upgrade/upg_sys_role_menu.sql index 1ed2f2d4..a206d4b8 100644 --- a/database/upgrade/upg_sys_role_menu.sql +++ b/database/upgrade/upg_sys_role_menu.sql @@ -157,6 +157,7 @@ INSERT IGNORE INTO `sys_role_menu` VALUES (2, 2154); INSERT IGNORE INTO `sys_role_menu` VALUES (2, 2155); INSERT IGNORE INTO `sys_role_menu` VALUES (2, 2156); INSERT IGNORE INTO `sys_role_menu` VALUES (2, 2157); +INSERT IGNORE INTO `sys_role_menu` VALUES (2, 2158); INSERT IGNORE INTO `sys_role_menu` VALUES (3, 1); INSERT IGNORE INTO `sys_role_menu` VALUES (3, 4); INSERT IGNORE INTO `sys_role_menu` VALUES (3, 5); diff --git a/src/framework/utils/file/file.go b/src/framework/utils/file/file.go index d1f90f01..25ad2585 100644 --- a/src/framework/utils/file/file.go +++ b/src/framework/utils/file/file.go @@ -351,5 +351,6 @@ func ParseUploadFileDir(subPath string) string { // filePath 上传文件路径 func ParseUploadFilePath(filePath string) string { prefix, dir := resourceUpload() - return strings.Replace(filePath, prefix, dir, 1) + absPath := strings.Replace(filePath, prefix, dir, 1) + return filepath.ToSlash(absPath) } diff --git a/src/modules/network_data/controller/udm_auth.go b/src/modules/network_data/controller/udm_auth.go index d916957c..525f5a6a 100644 --- a/src/modules/network_data/controller/udm_auth.go +++ b/src/modules/network_data/controller/udm_auth.go @@ -81,7 +81,7 @@ func (s *UDMAuthController) Info(c *gin.Context) { c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) return } - + // 网元主机的Telnet客户端 telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) if err != nil { c.JSON(200, result.ErrMsg(err.Error())) @@ -150,7 +150,7 @@ func (s *UDMAuthController) Add(c *gin.Context) { c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) return } - + // 网元主机的Telnet客户端 telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) if err != nil { c.JSON(200, result.ErrMsg(err.Error())) @@ -199,7 +199,7 @@ func (s *UDMAuthController) Adds(c *gin.Context) { c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) return } - + // 网元主机的Telnet客户端 telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) if err != nil { c.JSON(200, result.ErrMsg(err.Error())) @@ -247,7 +247,7 @@ func (s *UDMAuthController) Edit(c *gin.Context) { c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) return } - + // 网元主机的Telnet客户端 telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) if err != nil { c.JSON(200, result.ErrMsg(err.Error())) @@ -310,7 +310,7 @@ func (s *UDMAuthController) Remove(c *gin.Context) { c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) return } - + // 网元主机的Telnet客户端 telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) if err != nil { c.JSON(200, result.ErrMsg(err.Error())) @@ -357,7 +357,7 @@ func (s *UDMAuthController) Removes(c *gin.Context) { c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) return } - + // 网元主机的Telnet客户端 telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) if err != nil { c.JSON(200, result.ErrMsg(err.Error())) diff --git a/src/modules/network_data/controller/udm_sub.go b/src/modules/network_data/controller/udm_sub.go index d0d2e40e..99c0d2b4 100644 --- a/src/modules/network_data/controller/udm_sub.go +++ b/src/modules/network_data/controller/udm_sub.go @@ -82,7 +82,7 @@ func (s *UDMSubController) Info(c *gin.Context) { c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) return } - + // 网元主机的Telnet客户端 telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) if err != nil { c.JSON(200, result.ErrMsg(err.Error())) @@ -177,7 +177,7 @@ func (s *UDMSubController) Add(c *gin.Context) { c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) return } - + // 网元主机的Telnet客户端 telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) if err != nil { c.JSON(200, result.ErrMsg(err.Error())) @@ -231,7 +231,7 @@ func (s *UDMSubController) Adds(c *gin.Context) { c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) return } - + // 网元主机的Telnet客户端 telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) if err != nil { c.JSON(200, result.ErrMsg(err.Error())) @@ -284,7 +284,7 @@ func (s *UDMSubController) Edit(c *gin.Context) { c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) return } - + // 网元主机的Telnet客户端 telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) if err != nil { c.JSON(200, result.ErrMsg(err.Error())) @@ -389,7 +389,7 @@ func (s *UDMSubController) Remove(c *gin.Context) { c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) return } - + // 网元主机的Telnet客户端 telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) if err != nil { c.JSON(200, result.ErrMsg(err.Error())) @@ -436,7 +436,7 @@ func (s *UDMSubController) Removes(c *gin.Context) { c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) return } - + // 网元主机的Telnet客户端 telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) if err != nil { c.JSON(200, result.ErrMsg(err.Error())) diff --git a/src/modules/network_element/controller/action.go b/src/modules/network_element/controller/action.go index 1259ea98..e55f1c80 100644 --- a/src/modules/network_element/controller/action.go +++ b/src/modules/network_element/controller/action.go @@ -2,6 +2,7 @@ package controller import ( "fmt" + "os" "path/filepath" "runtime" "strings" @@ -9,6 +10,7 @@ import ( "be.ems/src/framework/i18n" "be.ems/src/framework/utils/ctx" "be.ems/src/framework/utils/file" + "be.ems/src/framework/utils/generate" "be.ems/src/framework/utils/ssh" "be.ems/src/framework/vo/result" neService "be.ems/src/modules/network_element/service" @@ -29,7 +31,7 @@ type NeActionController struct { neInfoService neService.INeInfo } -// 发送文件到网元端 +// 发送文件从本地到网元 // // POST /pushFile func (s *NeActionController) PushFile(c *gin.Context) { @@ -38,6 +40,7 @@ func (s *NeActionController) PushFile(c *gin.Context) { NeType string `json:"neType" binding:"required"` NeID string `json:"neId" binding:"required"` UploadPath string `json:"uploadPath" binding:"required"` + DelTemp bool `json:"delTemp"` // 删除本地临时文件 } if err := c.ShouldBindBodyWith(&body, binding.JSON); err != nil { c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) @@ -68,13 +71,20 @@ func (s *NeActionController) PushFile(c *gin.Context) { // 本地文件 localFilePath := file.ParseUploadFilePath(body.UploadPath) - neFilePath := fmt.Sprintf("/tmp/%s", filepath.Base(localFilePath)) + // 网元端临时目录 + sshClient.RunCMD("mkdir -p /tmp/omc/push && sudo chmod 777 -R /tmp/omc") + neFilePath := filepath.ToSlash(filepath.Join("/tmp/omc/push", filepath.Base(localFilePath))) // 复制到远程 if err = sftpClient.CopyFileLocalToRemote(localFilePath, neFilePath); err != nil { - c.JSON(200, result.ErrMsg(fmt.Sprintf("%s : please check if scp remote copy is allowed", neInfo.NeType))) + c.JSON(200, result.ErrMsg("Please check if the file exists or if scp is allowed to copy remotely")) return } + defer func() { + if body.DelTemp { + _ = os.Remove(localFilePath) + } + }() c.JSON(200, result.OkData(filepath.ToSlash(neFilePath))) } @@ -88,6 +98,7 @@ func (s *NeActionController) PullFile(c *gin.Context) { NeID string `form:"neId" binding:"required"` Path string `form:"path" binding:"required"` FileName string `form:"fileName" binding:"required"` + DelTemp bool `form:"delTemp"` // 删除本地临时文件 } if err := c.ShouldBindQuery(&querys); err != nil { c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) @@ -116,8 +127,9 @@ func (s *NeActionController) PullFile(c *gin.Context) { } defer sftpClient.Close() - nePath := fmt.Sprintf("%s/%s", querys.Path, querys.FileName) - localFilePath := fmt.Sprintf("/tmp/omc/pullFile%s", nePath) + nePath := filepath.ToSlash(filepath.Join(querys.Path, querys.FileName)) + fileName := generate.Code(6) + "_" + querys.FileName + localFilePath := filepath.Join("/tmp/omc/pull", fileName) if runtime.GOOS == "windows" { localFilePath = fmt.Sprintf("C:%s", localFilePath) } @@ -126,7 +138,13 @@ func (s *NeActionController) PullFile(c *gin.Context) { c.JSON(200, result.ErrMsg(err.Error())) return } - c.FileAttachment(localFilePath, querys.FileName) + + defer func() { + if querys.DelTemp { + _ = os.Remove(localFilePath) + } + }() + c.FileAttachment(localFilePath, fileName) } // 网元端文件列表 diff --git a/src/modules/network_element/ne_config_test.go b/src/modules/network_element/ne_config_test.go index cc626cee..5d693c3c 100644 --- a/src/modules/network_element/ne_config_test.go +++ b/src/modules/network_element/ne_config_test.go @@ -29,7 +29,7 @@ const ( // 配置文件路径 configParamDir = "../../../config/param" // configParamFile = "*" // 目录下全部更新 - configParamFile = "ims_param_config.yaml" // 单文件更新 + configParamFile = "cbc_param_config.yaml" // 单文件更新 ) func TestEncrypt(t *testing.T) { @@ -154,9 +154,9 @@ func saveDB(s model.NeConfig) string { s.UpdateTime = time.Now().UnixMilli() if id != "" { s.ID = id - db.Save(s) + db.Save(&s) } else { - db.Create(s) + db.Create(&s) } return s.ID } diff --git a/src/modules/network_element/repository/ne_info.impl.go b/src/modules/network_element/repository/ne_info.impl.go index 2559bd84..b50a39c5 100644 --- a/src/modules/network_element/repository/ne_info.impl.go +++ b/src/modules/network_element/repository/ne_info.impl.go @@ -31,6 +31,7 @@ var neListSort = []string{ "N3IWF", "MOCNGW", "SMSC", + "CBC", } // 实例化数据层 NeInfoImpl 结构体 diff --git a/src/modules/trace/controller/tcpdump.go b/src/modules/trace/controller/tcpdump.go index c932dd01..94012723 100644 --- a/src/modules/trace/controller/tcpdump.go +++ b/src/modules/trace/controller/tcpdump.go @@ -1,9 +1,13 @@ package controller import ( + "os" + "path/filepath" + "be.ems/src/framework/i18n" "be.ems/src/framework/utils/ctx" "be.ems/src/framework/vo/result" + neService "be.ems/src/modules/network_element/service" traceService "be.ems/src/modules/trace/service" "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" @@ -12,6 +16,7 @@ import ( // 实例化控制层 TcpdumpController 结构体 var NewTcpdump = &TcpdumpController{ TcpdumpService: traceService.NewTcpdumpImpl, + neInfoService: neService.NewNeInfoImpl, } // 信令抓包请求 @@ -20,6 +25,8 @@ var NewTcpdump = &TcpdumpController{ type TcpdumpController struct { // 信令抓包服务 TcpdumpService traceService.ITcpdump + // 网元信息服务 + neInfoService neService.INeInfo } // 网元抓包PACP 开始 @@ -30,7 +37,7 @@ func (s *TcpdumpController) DumpStart(c *gin.Context) { var body struct { NeType string `json:"neType" binding:"required"` // 网元类型 NeId string `json:"neId" binding:"required"` // 网元ID - Cmd string `json:"cmd" binding:"required"` // 命令 "-n -s 0 -v -w" + Cmd string `json:"cmd" binding:"required"` // 命令 "-n -s 0 -v" } err := c.ShouldBindBodyWith(&body, binding.JSON) if err != nil { @@ -38,21 +45,12 @@ func (s *TcpdumpController) DumpStart(c *gin.Context) { return } - fileName, err := s.TcpdumpService.DumpStart(body.NeType, body.NeId, body.Cmd) + taskCode, err := s.TcpdumpService.DumpStart(body.NeType, body.NeId, body.Cmd) if err != nil { - msg := err.Error() - if msg == "noData" { - // 找不到 %s %s 对应网元信息 - msg = i18n.TTemplate(language, "trace.tcpdump.noData", map[string]any{"type": body.NeType, "id": body.NeId}) - } - c.JSON(200, result.ErrMsg(msg)) + c.JSON(200, result.ErrMsg(i18n.TKey(language, err.Error()))) return } - c.JSON(200, result.OkData(map[string]any{ - "msg": "tcpdump started", - "out": fileName, - "log": "", - })) + c.JSON(200, result.OkData(taskCode)) } // 网元抓包PACP 结束 @@ -61,9 +59,9 @@ func (s *TcpdumpController) DumpStart(c *gin.Context) { func (s *TcpdumpController) DumpStop(c *gin.Context) { language := ctx.AcceptLanguage(c) var body struct { - NeType string `json:"neType" binding:"required"` // 网元类型 - NeId string `json:"neId" binding:"required"` // 网元ID - FileName string `json:"fileName"` // 文件名 查看日志信息 + NeType string `json:"neType" binding:"required"` // 网元类型 + NeId string `json:"neId" binding:"required"` // 网元ID + TaskCode string `json:"taskCode" binding:"required"` // 任务码,停止任务并查看日志信息 } err := c.ShouldBindBodyWith(&body, binding.JSON) if err != nil { @@ -71,29 +69,48 @@ func (s *TcpdumpController) DumpStop(c *gin.Context) { return } - logMsg, err := s.TcpdumpService.DumpStop(body.NeType, body.NeId, body.FileName) + taskLog, err := s.TcpdumpService.DumpStop(body.NeType, body.NeId, body.TaskCode) if err != nil { - msg := err.Error() - if msg == "noData" { - // 找不到 %s %s 对应网元信息 - msg := i18n.TTemplate(language, "trace.tcpdump.noData", map[string]any{"type": body.NeType, "id": body.NeId}) - c.JSON(200, result.ErrMsg(msg)) - return - } - c.JSON(200, result.ErrMsg(msg)) + c.JSON(200, result.ErrMsg(i18n.TKey(language, err.Error()))) return } - c.JSON(200, result.OkData(map[string]any{ - "msg": "tcpdump stopped", - "out": body.FileName, - "log": logMsg, - })) + c.JSON(200, result.OkMsg(taskLog)) +} + +// 网元抓包PACP 下载 +// +// GET /download +func (s *TcpdumpController) DumpDownload(c *gin.Context) { + language := ctx.AcceptLanguage(c) + var query struct { + NeType string `form:"neType" binding:"required"` // 网元类型 + NeID string `form:"neId" binding:"required"` // 网元ID + TaskCode string `form:"taskCode" binding:"required"` // 任务码,停止任务并查看日志信息 + DelTemp bool `form:"delTemp"` // 完成后是否删除本地临时zip文件 + } + if err := c.ShouldBindQuery(&query); err != nil { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + zipFilePath, err := s.TcpdumpService.DumpDownload(query.NeType, query.NeID, query.TaskCode) + if err != nil { + c.JSON(200, result.ErrMsg(i18n.TKey(language, err.Error()))) + return + } + + defer func() { + if query.DelTemp { + _ = os.Remove(zipFilePath) + } + }() + c.FileAttachment(zipFilePath, filepath.Base(zipFilePath)) } // UPF标准版内部抓包 // -// POST /traceUPF -func (s *TcpdumpController) TraceUPF(c *gin.Context) { +// POST /upf +func (s *TcpdumpController) UPFTrace(c *gin.Context) { language := ctx.AcceptLanguage(c) var body struct { NeType string `json:"neType" binding:"required"` // 网元类型 @@ -106,19 +123,10 @@ func (s *TcpdumpController) TraceUPF(c *gin.Context) { return } - fileName, logMsg, err := s.TcpdumpService.DumpUPF(body.NeType, body.NeId, body.Cmd) + msg, err := s.TcpdumpService.UPFTrace(body.NeType, body.NeId, body.Cmd) if err != nil { - msg := err.Error() - if msg == "noData" { - // 找不到 %s %s 对应网元信息 - msg = i18n.TTemplate(language, "trace.tcpdump.noData", map[string]any{"type": body.NeType, "id": body.NeId}) - } - c.JSON(200, result.ErrMsg(msg)) + c.JSON(200, result.ErrMsg(i18n.TKey(language, err.Error()))) return } - c.JSON(200, result.OkData(map[string]any{ - "msg": "trace UPF dump pacp", - "out": fileName, - "log": logMsg, - })) + c.JSON(200, result.OkData(msg)) } diff --git a/src/modules/trace/service/tcpdump.go b/src/modules/trace/service/tcpdump.go index cc746064..9f77fb06 100644 --- a/src/modules/trace/service/tcpdump.go +++ b/src/modules/trace/service/tcpdump.go @@ -2,12 +2,15 @@ package service // 信令抓包 服务层接口 type ITcpdump interface { - // DumpStart 触发tcpdump开始抓包 filePcapName, err + // DumpStart 触发tcpdump开始抓包 DumpStart(neType, neId, cmdStr string) (string, error) // DumpStop 停止已存在抓包句柄 - DumpStop(neType, neId, fileName string) (string, error) + DumpStop(neType, neId, taskCode string) (string, error) - // DumpUPF UPF标准版抓包 - DumpUPF(neType, neId, cmdStr string) (string, string, error) + // DumpDownload 抓包文件网元端复制到本地输出zip文件 + DumpDownload(neType, neId, taskCode string) (string, error) + + // UPFTrace UPF标准版内部抓包 + UPFTrace(neType, neId, cmdStr string) (string, error) } diff --git a/src/modules/trace/service/tcpdump.impl.go b/src/modules/trace/service/tcpdump.impl.go index 005ee2b8..72194832 100644 --- a/src/modules/trace/service/tcpdump.impl.go +++ b/src/modules/trace/service/tcpdump.impl.go @@ -2,36 +2,45 @@ package service import ( "fmt" + "os" + "path/filepath" + "regexp" + "runtime" "strings" + "sync" "time" "be.ems/src/framework/logger" - "be.ems/src/framework/utils/date" + "be.ems/src/framework/utils/file" neService "be.ems/src/modules/network_element/service" ) // 实例化服务层 TcpdumpImpl 结构体 var NewTcpdumpImpl = &TcpdumpImpl{ neInfoService: neService.NewNeInfoImpl, - tcpdumpPIDMap: map[string]string{}, } // 信令抓包 服务层处理 type TcpdumpImpl struct { // 网元信息服务 neInfoService neService.INeInfo - // 抓包进程PID - tcpdumpPIDMap map[string]string } -// DumpStart 触发tcpdump开始抓包 filePcapName, err +// 抓包进程PID +var dumpPIDMap sync.Map + +// DumpStart 触发tcpdump开始抓包 func (s *TcpdumpImpl) DumpStart(neType, neId, cmdStr string) (string, error) { + // 命令检查 + if strings.Contains(cmdStr, "w") { + return "", fmt.Errorf("command cannot contain -w") + } + // 查询网元获取IP neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID(neType, neId) if neInfo.NeId != neId || neInfo.IP == "" { - return "", fmt.Errorf("noData") + return "", fmt.Errorf("app.common.noNEInfo") } - // 网元主机的SSH客户端 sshClient, err := s.neInfoService.NeRunSSHClient(neInfo.NeType, neInfo.NeId) if err != nil { @@ -39,47 +48,53 @@ func (s *TcpdumpImpl) DumpStart(neType, neId, cmdStr string) (string, error) { } defer sshClient.Close() - // 是否拥有sudo权限并拼接 - withSudo := "" - if _, err := sshClient.RunCMD("sudo -n uname"); err == nil { - withSudo = "sudo " - } - - if msg, err := sshClient.RunCMD(fmt.Sprintf("%s tcpdump --version", withSudo)); err != nil { - // stderr: bash: tcpdump:未找到命令 => exit status 127 + // 检查是否安装tcpdump + if msg, err := sshClient.RunCMD("sudo tcpdump --version"); err != nil { + // bash: tcpdump: command not found msg = strings.TrimSpace(msg) - logger.Warnf("DumpStart err: %s => %s", msg, err.Error()) + logger.Errorf("DumpStart err: %s => %s", msg, err.Error()) return "", fmt.Errorf(msg) } - // 拼装命令 - neTypeID := fmt.Sprintf("%s_%s", neInfo.NeType, neInfo.NeId) - timeStr := date.ParseDateToStr(time.Now(), date.YYYYMMDDHHMMSS) - fileName := fmt.Sprintf("%s_%s", timeStr, neTypeID) - sendCmd := fmt.Sprintf("cd /tmp \n %s nohup timeout 30m tcpdump -i any %s -s0 -w %s.pcap > %s.log 2>&1 & \necho $!", withSudo, cmdStr, fileName, fileName) - // cd /tmp - // sudo nohup timeout 60m tcpdump -i any -n -s 0 -v -w -s0 -w 20240115140822_UDM_001.pcap > 20240115140822_UDM_001.log 2>&1 & echo $! - msg, err := sshClient.RunCMD(sendCmd) - msg = strings.TrimSpace(msg) - if err != nil || strings.HasPrefix(msg, "stderr:") { - logger.Warnf("DumpStart err: %s => %s", msg, err.Error()) + taskCode := time.Now().Format("20060102150405") + // 存放文件目录 /tmp/omc/tcpdump/udm/001/20240817104241 + neDirTemp := fmt.Sprintf("/tmp/omc/tcpdump/%s/%s/%s", strings.ToLower(neInfo.NeType), neInfo.NeId, taskCode) + sshClient.RunCMD(fmt.Sprintf("mkdir -p %s && sudo chmod 777 -R /tmp/omc", neDirTemp)) + + // 命令拼装 + logPath := fmt.Sprintf("%s/tcpdump.log", neDirTemp) + filePath := fmt.Sprintf("%s/part_%s.pcap ", neDirTemp, taskCode) + if strings.Contains(cmdStr, "-G") { + filePath = fmt.Sprintf("%s/part_%%Y%%m%%d%%H%%M%%S.pcap ", neDirTemp) + } + sendCmd := fmt.Sprintf("sudo timeout 60m sudo tcpdump -i any %s -w %s > %s 2>&1 & echo $!", cmdStr, filePath, logPath) + // sudo timeout 60m sudo tcpdump -i any -n -s 0 -v -G 60 -W 6 -w /tmp/omc/tcpdump/udm/001/20240817104241/part_%Y-%m-%d_%H:%M:%S.pcap > /tmp/omc/tcpdump/udm/001/20240817104241/tcpdump.log 2>&1 & echo $! + // sudo timeout 60m sudo tcpdump -i any -n -s 0 -v -w /tmp/omc/tcpdump/udm/001/20240817105440/part_2024-08-17_10:54:40.pcap > /tmp/omc/tcpdump/udm/001/20240817105440/tcpdump.log 2>&1 & echo $! + // + // timeout 超时60分钟后发送kill命令,1分钟后强制终止命令。tcpdump -G 文件轮转间隔时间(秒) -W 文件轮转保留最近数量 + // sudo timeout --kill-after=1m 60m sudo tcpdump -i any -n -s 0 -v -G 10 -W 7 -w /tmp/part_%Y%m%d%H%M%S.pcap > /tmp/part.log 2>&1 & echo $! + // sudo kill $(pgrep -P 722729) + outputPID, err := sshClient.RunCMD(sendCmd) + outputPID = strings.TrimSpace(outputPID) + if err != nil || strings.HasPrefix(outputPID, "stderr:") { + logger.Errorf("DumpStart err: %s => %s", outputPID, err.Error()) return "", err } // 检查进程 ps aux | grep tcpdump // 强杀 sudo pkill tcpdump - s.tcpdumpPIDMap[neTypeID] = msg - return fileName, err + pidKey := fmt.Sprintf("%s_%s_%s", strings.ToLower(neInfo.NeType), neInfo.NeId, taskCode) + dumpPIDMap.Store(pidKey, outputPID) + return taskCode, err } // DumpStop 停止已存在抓包句柄 -func (s *TcpdumpImpl) DumpStop(neType, neId, fileName string) (string, error) { +func (s *TcpdumpImpl) DumpStop(neType, neId, taskCode string) (string, error) { // 查询网元获取IP neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID(neType, neId) if neInfo.NeId != neId || neInfo.IP == "" { - return "", fmt.Errorf("noData") + return "", fmt.Errorf("app.common.noNEInfo") } - // 网元主机的SSH客户端 sshClient, err := s.neInfoService.NeRunSSHClient(neInfo.NeType, neInfo.NeId) if err != nil { @@ -87,107 +102,147 @@ func (s *TcpdumpImpl) DumpStop(neType, neId, fileName string) (string, error) { } defer sshClient.Close() - // 是否拥有sudo权限并拼接 - withSudo := "" - if _, err := sshClient.RunCMD("sudo -n uname"); err == nil { - withSudo = "sudo " - } - - // 是否存在进程 - neTypeID := fmt.Sprintf("%s_%s", neInfo.NeType, neInfo.NeId) - pid, ok := s.tcpdumpPIDMap[neTypeID] + // 是否存在执行过的进程 + pidKey := fmt.Sprintf("%s_%s_%s", strings.ToLower(neInfo.NeType), neInfo.NeId, taskCode) + pid, ok := dumpPIDMap.Load(pidKey) if !ok || pid == "" { return "", fmt.Errorf("tcpdump is not running") } + defer dumpPIDMap.Delete(pidKey) - // 查看日志 - viewLogFile := "" - if fileName != "" && strings.Contains(fileName, neTypeID) { - viewLogFile = fmt.Sprintf("\n cat %s.log", fileName) - } - - // 拼装命令 - sendCmd := fmt.Sprintf("cd /tmp \n %s kill %s %s", withSudo, pid, viewLogFile) - msg, err := sshClient.RunCMD(sendCmd) - delete(s.tcpdumpPIDMap, neTypeID) - if err != nil || strings.HasPrefix(msg, "stderr:") { - logger.Warnf("DumpStop err: %s => %s", strings.TrimSpace(msg), err.Error()) + // 存放文件目录 /tmp/omc/tcpdump/udm/001/20240817104241 + neDirTemp := fmt.Sprintf("/tmp/omc/tcpdump/%s/%s/%s", strings.ToLower(neInfo.NeType), neInfo.NeId, taskCode) + // 命令拼装 + sendCmd := fmt.Sprintf("pids=$(pgrep -P %s) && [ -n \"$pids\" ] && sudo kill $pids;sudo timeout 2s cat %s/tcpdump.log", pid, neDirTemp) + // pids=$(pgrep -P 1914341) && [ -n "$pids" ] && sudo kill $pids;sudo timeout 2s cat tcpdump.log + output, err := sshClient.RunCMD(sendCmd) + if err != nil || strings.HasPrefix(output, "stderr:") { + logger.Warnf("DumpStop err: %s => %s", strings.TrimSpace(output), err.Error()) return "", err } - return msg, nil + return output, nil } -// DumpUPF UPF标准版抓包 -func (s *TcpdumpImpl) DumpUPF(neType, neId, cmdStr string) (string, string, error) { +// DumpDownload 抓包文件网元端复制到本地输出zip文件 +func (s *TcpdumpImpl) DumpDownload(neType, neId, taskCode string) (string, error) { // 查询网元获取IP neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID(neType, neId) if neInfo.NeId != neId || neInfo.IP == "" { - return "", "", fmt.Errorf("noData") + return "", fmt.Errorf("app.common.noNEInfo") } - // 网元主机的SSH客户端 sshClient, err := s.neInfoService.NeRunSSHClient(neInfo.NeType, neInfo.NeId) if err != nil { - return "", "", err + return "", err } defer sshClient.Close() + // 网元主机的SSH客户端进行文件传输 + sftpClient, err := sshClient.NewClientSFTP() + if err != nil { + return "", fmt.Errorf("ne info sftp client err") + } + defer sftpClient.Close() - // 是否拥有sudo权限并拼接 - withSudo := "" - if _, err := sshClient.RunCMD("sudo -n uname"); err == nil { - withSudo = "sudo " + neTypeLower := strings.ToLower(neInfo.NeType) + // 网管本地路径 + localDirPath := fmt.Sprintf("/tmp/omc/tcpdump/%s/%s", neTypeLower, neInfo.NeId) + if runtime.GOOS == "windows" { + localDirPath = fmt.Sprintf("C:%s", localDirPath) } - if msg, err := sshClient.RunCMD(fmt.Sprintf("%s expect -version", withSudo)); err != nil { - // stderr: bash: expect:未找到命令 => exit status 127 - msg = strings.TrimSpace(msg) - logger.Warnf("DumpUPF err: %s => %s", msg, err.Error()) - return "", "", fmt.Errorf(msg) + // 网元pcap目录 /tmp/omc/tcpdump/udm/001/20240817104241 + sshClient.RunCMD("mkdir -p /tmp/omc && sudo chmod 777 -R /tmp/omc") + neDirTemp := fmt.Sprintf("/tmp/omc/tcpdump/%s/%s/%s", neTypeLower, neInfo.NeId, taskCode) + // 网元端复制到本地 + localDirFilePath := filepath.Join(localDirPath, taskCode) + if err = sftpClient.CopyDirRemoteToLocal(neDirTemp, localDirFilePath); err != nil { + return "", fmt.Errorf("copy tcpdump file err") } - // 拼装命令 - neTypeID := fmt.Sprintf("%s_%s", neInfo.NeType, neInfo.NeId) - timeStr := date.ParseDateToStr(time.Now(), date.YYYYMMDDHHMMSS) - fileName := fmt.Sprintf("%s_%s", timeStr, neTypeID) - // UPF标准版本telnet脚本 - scriptStr := "set pcapCmd [lindex $argv 0]\nspawn telnet " + neInfo.IP + " 5002\nexpect \"upfd1# \"\nsend \"$pcapCmd\\n\"\nexpect \"upfd1# \"\nsend \"quit\\n\"\nexpect \"eof\"" - // scriptStr := "set pcapCmd [lindex $argv 0]\nspawn telnet localhost 5002\nexpect \"upfd1# \"\nsend \"$pcapCmd\\n\"\nexpect \"upfd1# \"\nsend \"quit\\n\"\nexpect \"eof\"" - writePcapFile := fmt.Sprintf("echo '%s' > pcapUPF.sh\n %s chmod +x pcapUPF.sh", scriptStr, withSudo) - writeLogFile := fmt.Sprintf("> %s.log 2>&1 \ncat %s.log", fileName, fileName) + // 压缩zip文件名 + zipFileName := fmt.Sprintf("%s-%s-pcap-%s.zip", neTypeLower, neInfo.NeId, taskCode) + zipFilePath := filepath.Join(localDirPath, zipFileName) + if err := file.CompressZipByDir(zipFilePath, localDirFilePath); err != nil { + return "", fmt.Errorf("compress zip err") + } + _ = os.RemoveAll(localDirFilePath) // 删除本地临时目录 + return zipFilePath, nil +} + +// UPFTrace UPF标准版内部抓包 +func (s *TcpdumpImpl) UPFTrace(neType, neId, cmdStr string) (string, error) { + // 命令检查 + if strings.Contains(cmdStr, "file") { + return "", fmt.Errorf("command cannot contain file") + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID(neType, neId) + if neInfo.NeId != neId || neInfo.IP == "" { + return "", fmt.Errorf("app.common.noNEInfo") + } + // 网元主机的SSH客户端 + sshClient, err := s.neInfoService.NeRunSSHClient(neInfo.NeType, neInfo.NeId) + if err != nil { + return "", err + } + defer sshClient.Close() + // 网元主机的Telnet客户端 + telnetClient, err := s.neInfoService.NeRunTelnetClient("UPF", neInfo.NeId, 2) + if err != nil { + return "", err + } + defer telnetClient.Close() + + // 命令拼装 + fileName := fmt.Sprintf("%s_%s_part_%s.pcap ", neInfo.NeType, neInfo.NeId, time.Now().Format("20060102150405")) + pcapCmd := fmt.Sprintf("%s\r\n", cmdStr) // 以off结尾是停止抓包,不需要写文件 - pcapCmd := cmdStr - if !strings.HasSuffix(pcapCmd, "off") { - pcapCmd = fmt.Sprintf("%s file %s.pcap", cmdStr, fileName) + if !strings.Contains(cmdStr, "off") { + // pcap trace rx tx max 100000 intfc any file UPF_001_part_20240817164516.pcap + pcapCmd = fmt.Sprintf("%s file %s\r\n", cmdStr, fileName) } - sendCmd := fmt.Sprintf("cd /tmp \n%s\n expect ./pcapUPF.sh '%s' %s", writePcapFile, pcapCmd, writeLogFile) - // cd /tmp - // echo '' > - // expect ./cap.sh > pcapUPF.sh - // sudo chmod +x pcapUPF.sh - // expect ./cap.sh 'pcap dispatch trace off' > 20240115165701_UDM_001.log 2>&1 - // cat 20240115165701_UDM_001.log - msg, err := sshClient.RunCMD(sendCmd) - msg = strings.TrimSpace(msg) - if err != nil || strings.HasPrefix(msg, "stderr:") { - logger.Warnf("DumpUPF err: %s => %s", msg, err.Error()) - return "", "", err + // 发送命令 UPF内部默认输出路径/tmp只能写文件名 + // pcap trace rx tx max 100000 intfc any file upf_test.pcap + // pcap trace rx tx off + output, err := telnetClient.RunCMD(pcapCmd) + if err != nil { + logger.Warnf("DumpUPF err: %s => %s", output, err.Error()) + return "", err } - if strings.Contains(msg, "Unable to connect to remote host") { - return "", "", fmt.Errorf("connection refused") + + // 结果截取 + arr := strings.Split(output, "\r\n") + if len(arr) == 2 { + return "", fmt.Errorf("trace pacp run failed") } - // 以off结尾是停止抓包,不需要写文件 - if strings.HasSuffix(pcapCmd, "off") { - if strings.Contains(msg, "Write ") { - lastTmpIndex := strings.LastIndex(msg, "/tmp/") - text := msg[lastTmpIndex+5:] - extensionIndex := strings.LastIndex(text, ".pcap") - if extensionIndex != -1 { - fileName = text[:extensionIndex] + if len(arr) > 3 { + resMsg := arr[2] + // pcap trace: unknown input `f file UPF_001_part_2024-08-19...' + // pcap trace: dispatch trace already enabled... + // pcap trace: dispatch trace already disabled... + // pcap trace: No packets captured... + // Write 100000 packets to /tmp/UPF_001_part_20240817164516.pcap, and stop capture... + if strings.Contains(resMsg, "unknown input") { + return "", fmt.Errorf("trace pacp unknown input") + } + if strings.Contains(resMsg, "already enabled") { + return "", fmt.Errorf("trace pacp already running") + } + if strings.Contains(resMsg, "already disabled") { + return "", fmt.Errorf("trace pacp not running") + } + if strings.Contains(resMsg, "No packets") { + return "", fmt.Errorf("trace pacp not packets") + } + if strings.Contains(resMsg, "packets to") { + matches := regexp.MustCompile(`(/tmp/[^,\s]+)`).FindStringSubmatch(resMsg) + if len(matches) == 0 { + return "", fmt.Errorf("file path not found") } - } else { - fileName = "" + return matches[0], nil } } - return fileName, msg, err + return "trace pacp running", nil } diff --git a/src/modules/trace/trace.go b/src/modules/trace/trace.go index dd38c724..feef0d16 100644 --- a/src/modules/trace/trace.go +++ b/src/modules/trace/trace.go @@ -28,10 +28,14 @@ func Setup(router *gin.Engine) { collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.tcpdump", collectlogs.BUSINESS_TYPE_OTHER)), controller.NewTcpdump.DumpStop, ) - tcpdumpGroup.POST("/traceUPF", + tcpdumpGroup.GET("/download", + middleware.PreAuthorize(nil), + controller.NewTcpdump.DumpDownload, + ) + tcpdumpGroup.POST("/upf", middleware.PreAuthorize(nil), collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.tcpdump", collectlogs.BUSINESS_TYPE_OTHER)), - controller.NewTcpdump.TraceUPF, + controller.NewTcpdump.UPFTrace, ) } }