├── .github └── ISSUE_TEMPLATE │ ├── 1bug.yaml │ ├── 2feature.yaml │ ├── 3consultation.yaml │ └── config.yml ├── .gitignore ├── LICENSE ├── README.md ├── README_en.md ├── auto_discover ├── README.md └── plugin_mysql.py ├── docs └── images │ ├── wechat.jpg │ └── wechat.png ├── messenger └── README.md ├── netTopology ├── .gitignore ├── README.md ├── discover │ ├── collector.go │ ├── entry.go │ ├── layer2.go │ ├── layer3.go │ ├── master.go │ ├── models.go │ └── var.go ├── go.mod ├── go.sum ├── img.png ├── internal │ ├── common.go │ ├── node_type.go │ ├── oids.go │ ├── types.go │ └── utils.go ├── lldp.md └── main.go ├── secret ├── Pipfile ├── README.md ├── README_en.md ├── inner.py └── vault.py └── zabbix2tsdb ├── go.mod ├── go.sum ├── readme.md └── zabbix.go /.github/ISSUE_TEMPLATE/1bug.yaml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | title: "[Bug]: " 4 | labels: ["☢️ bug"] 5 | assignees: 6 | - Selina316 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | Thanks for taking the time to fill out this bug report! 12 | - type: input 13 | id: contact 14 | attributes: 15 | label: Contact Details 16 | description: How can we get in touch with you if we need more info? 17 | placeholder: ex. email@example.com 18 | validations: 19 | required: false 20 | - type: dropdown 21 | id: aspects 22 | attributes: 23 | label: This bug is related to UI or API? 24 | multiple: true 25 | options: 26 | - UI 27 | - API 28 | - type: textarea 29 | id: happened 30 | attributes: 31 | label: What happened? 32 | description: Also tell us, what did you expect to happen? 33 | placeholder: Tell us what you see! 34 | value: "A bug happened!" 35 | validations: 36 | required: true 37 | - type: input 38 | id: version 39 | attributes: 40 | label: Version 41 | description: What version of our software are you running? 42 | value: "newest" 43 | validations: 44 | required: true 45 | - type: dropdown 46 | id: browsers 47 | attributes: 48 | label: What browsers are you seeing the problem on? 49 | multiple: true 50 | options: 51 | - Firefox 52 | - Chrome 53 | - Safari 54 | - Microsoft Edge 55 | - type: textarea 56 | id: logs 57 | attributes: 58 | label: Relevant log output 59 | description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. 60 | render: shell 61 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2feature.yaml: -------------------------------------------------------------------------------- 1 | name: Feature wanted 2 | description: A new feature would be good 3 | title: "[Feature]: " 4 | labels: ["✏️ feature"] 5 | assignees: 6 | - pycook 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | Thank you for your feature suggestion; we will evaluate it carefully! 12 | - type: input 13 | id: contact 14 | attributes: 15 | label: Contact Details 16 | description: How can we get in touch with you if we need more info? 17 | placeholder: ex. email@example.com 18 | validations: 19 | required: false 20 | - type: dropdown 21 | id: aspects 22 | attributes: 23 | label: feature is related to UI or API aspects? 24 | multiple: true 25 | options: 26 | - UI 27 | - API 28 | - type: textarea 29 | id: feature 30 | attributes: 31 | label: What is your advice? 32 | description: Also tell us, what did you expect to happen? 33 | placeholder: Tell us what you want! 34 | value: "everyone wants this feature!" 35 | validations: 36 | required: true 37 | - type: input 38 | id: version 39 | attributes: 40 | label: Version 41 | description: What version of our software are you running? 42 | value: "newest" 43 | validations: 44 | required: true 45 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/3consultation.yaml: -------------------------------------------------------------------------------- 1 | name: Help wanted 2 | description: I have a question 3 | title: "[help wanted]: " 4 | labels: ["help wanted"] 5 | assignees: 6 | - ivonGwy 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | Please tell us what's you need! 12 | - type: input 13 | id: contact 14 | attributes: 15 | label: Contact Details 16 | description: How can we get in touch with you if we need more info? 17 | placeholder: ex. email@example.com 18 | validations: 19 | required: false 20 | - type: textarea 21 | id: question 22 | attributes: 23 | label: What is your question? 24 | description: Also tell us, how can we help? 25 | placeholder: Tell us what you need! 26 | value: "i have a question!" 27 | validations: 28 | required: true 29 | - type: input 30 | id: version 31 | attributes: 32 | label: Version 33 | description: What version of our software are you running? 34 | value: "newest" 35 | validations: 36 | required: true 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: veops official website 4 | url: https://veops.cn/#hero 5 | about: you can contact us here. 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | .idea 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [English](README_en.md) 2 | # ops-tools 3 | 4 | 运维过程中一些常用的实践和代码,将一些运维中的小工具不断加入进来,避免重复造轮子 5 | 6 | ### 独立组件 7 | 8 | #### golang 9 | [zabbix2tsdb](zabbix2tsdb/readme.md) 将zabbix的监控数据转换为prometheus格式输出 10 | 11 | [netTopology](netTopology) 基于SNMP协议自动发现局域网网络拓扑关系,输出拓扑数据结构 12 | 13 | [messenger](https://github.com/veops/messenger) 简单易用的消息发送服务 14 | 15 | 16 | #### python 17 | [secret](secret/README.md) 封装好的敏感数据存储工具, 自实现或者对接vault 18 | 19 | [auto_discover](auto_discover/README.md) 实例自动发现,主要用于cmdb自发现插件中 20 | 21 | 22 | ### TODO 23 | [netTopology]() 基于现有输出的结构化数据绘制网络拓扑图 24 | 25 | 26 | --- 27 | _**欢迎关注公众号(维易科技OneOps),关注后可加入微信群,进行产品和技术交流。**_ 28 | 29 |

30 | 公众号: 维易科技OneOps 31 |

32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /README_en.md: -------------------------------------------------------------------------------- 1 | [简体中文](README.md) 2 | # ops-tools 3 | 4 | Some common practices and code used in the operation and maintenance process. 5 | To avoid reinventing the wheel, strive to continuously incorporate some small 6 | tools used in operation and maintenance 7 | 8 | ### Individual components 9 | 10 | [zabbix2tsdb](zabbix2tsdb/readme.md) Convert monitoring data in Zabbix to Prometheus format for output. 11 | 12 | [netTopology](netTopology) Automatically discover the local area network (LAN) topology 13 | based on the SNMP protocol and output the topology data structure 14 | 15 | [messenger](messenger/README.md) simple and useful message sending server 16 | 17 | [secret](secret/README.md) Packaged sensitive data storage tool, inner method or Integrating with Vault 18 | 19 | [auto_discover](auto_discover/README.md) Instance auto-discovery, primarily used in CMDB auto-discovery plugins. 20 | 21 | 22 | ### TODO 23 | [netTopology]() Draw a network topology diagram based on the existing structured data output. 24 | 25 | --- 26 | 27 | _**Stay connected with us through our official WeChat account. Click to contact us and join our WeChat/QQ operations and maintenance groups to get more product and industry-related information.**_ 28 | 29 |

30 | 公众号: 维易科技OneOps 31 |

-------------------------------------------------------------------------------- /auto_discover/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/ops-tools/c8912d0e51de78cfed487f57553ccf3f6b4d394e/auto_discover/README.md -------------------------------------------------------------------------------- /auto_discover/plugin_mysql.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | from __future__ import unicode_literals 4 | import importlib 5 | import json 6 | import os 7 | import socket 8 | import subprocess 9 | import sys 10 | import threading 11 | import time 12 | 13 | try: 14 | import configparser 15 | import ipaddress 16 | import psutil 17 | import tempfile 18 | except: 19 | pass 20 | 21 | global_scan_ip = "10.0.2.15" # Identify on which device the subnet scanning is performed. 22 | cidrs = ["192.168.20.8/30"] # Subnet to be scanned. 23 | global_ports_range = "3306-3310,3320" # ports to be scanned. such as "3306-3310,3320" 24 | 25 | paths = ["/etc/my.cnf", "/etc/mysql/my.cnf"] 26 | threading_number = 10 27 | pip_modules = ["configparser", "ipaddress", "psutil", "tempfile"] 28 | pip_index_url = 'https://pypi.douban.com/simple' 29 | 30 | 31 | class Module: 32 | 33 | def __init__(self, modules=None, index_url=None): 34 | self.modules = modules 35 | self.index_url = pip_index_url 36 | if not modules: 37 | self.modules = pip_modules 38 | if not index_url: 39 | self.index_url = pip_index_url 40 | 41 | def check_install_modules(self): 42 | for v in self.modules: 43 | self.install_missing_module(v) 44 | 45 | def install_missing_module(self, module_name): 46 | try: 47 | importlib.import_module(module_name) 48 | except ImportError: 49 | try: 50 | subprocess.check_output(["pip", "--version"]) 51 | except subprocess.CalledProcessError: 52 | print("pips has not been installed, try to install...") 53 | subprocess.check_call([sys.executable, "-m", "ensurepip"]) 54 | 55 | try: 56 | subprocess.check_call([sys.executable, "-m", "pip", "install", '--index-url', 57 | self.index_url, module_name]) 58 | print("module '{}' has been installed successfully.".format(module_name)) 59 | except Exception as e: 60 | print("Failed to install module '{}': {}".format(module_name, str(e))) 61 | 62 | 63 | class Mysql: 64 | 65 | def __init__(self): 66 | pass 67 | 68 | @classmethod 69 | def parse_version(cls, sock): 70 | sock.sendall(b"\x00\x00\x00\x0a\x03\x00\x00\x00\x1b") 71 | data = sock.recv(1024) 72 | for encoding in ["latin-1", "utf-8", "ascii"]: 73 | try: 74 | d = data[5:].decode(encoding) 75 | if "MYSQL" in d.upper(): 76 | return data[5:].decode(encoding).split("\x00")[0] 77 | return "" 78 | except socket.error: 79 | continue 80 | return "" 81 | 82 | 83 | class Scan: 84 | 85 | def __init__(self, cidrs, port_range, paths=None, g_scan_ip=None): 86 | self.cidrs = cidrs 87 | self.ports = Utils.parse_port_range(port_range) 88 | self.local_ip = Utils.get_local_ip() 89 | if not paths: 90 | self.config_paths = ['/etc/mysql/my.cnf'] 91 | else: 92 | self.config_paths = paths 93 | self.global_scan_ip = g_scan_ip 94 | 95 | def get_from_ip_range(self): 96 | instances = [] 97 | if self.global_scan_ip != self.local_ip: 98 | return instances 99 | 100 | semaphore = threading.Semaphore(threading_number) 101 | 102 | def worker(scan_ip, s_port): 103 | r1 = self.scan_port(scan_ip, s_port) 104 | if r1: 105 | instances.append(r1) 106 | semaphore.release() 107 | 108 | threads = [] 109 | for cidr in self.cidrs: 110 | ip_network = ipaddress.ip_network(cidr) 111 | 112 | for ip in ip_network.hosts(): 113 | ip = str(ip) 114 | 115 | for port in self.ports: 116 | semaphore.acquire() 117 | 118 | t = threading.Thread(target=worker, args=(ip, port)) 119 | t.daemon = True 120 | threads.append(t) 121 | t.start() 122 | 123 | for i in threads: 124 | i.join() 125 | 126 | return instances 127 | 128 | @classmethod 129 | def scan_port(cls, ip, port): 130 | try: 131 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 132 | sock.settimeout(5) 133 | 134 | if sock.connect_ex((ip, port)) == 0: 135 | version = Mysql.parse_version(sock) 136 | if version: 137 | return ip, port, version 138 | 139 | sock.close() 140 | except socket.error: 141 | return None 142 | return None 143 | 144 | @classmethod 145 | def local_listening_ports(cls): 146 | listening_ports = [] 147 | for conn in psutil.net_connections(): 148 | if conn.status == 'LISTEN': 149 | listening_ports.append(conn.laddr.port) 150 | return listening_ports 151 | 152 | def get_local_listening_ports(self): 153 | listening_ports = self.local_listening_ports() 154 | instances = [] 155 | 156 | def worker(scan_ip, scan_port): 157 | r1 = self.scan_port(scan_ip, scan_port) 158 | if r1: 159 | instances.append(r1) 160 | semaphore.release() 161 | 162 | semaphore = threading.Semaphore(threading_number) 163 | threads = [] 164 | for port in listening_ports: 165 | semaphore.acquire() 166 | 167 | t = threading.Thread(target=worker, args=(self.local_ip, port)) 168 | t.daemon = True 169 | threads.append(t) 170 | t.start() 171 | 172 | for i in threads: 173 | i.join() 174 | 175 | return instances 176 | 177 | # this function will deprecate in the future 178 | def get_from_config(self): 179 | instances = [] 180 | if self.local_ip == "": 181 | return instances 182 | for path in self.config_paths: 183 | config = configparser.ConfigParser() 184 | config.read(path) 185 | 186 | if 'mysqld' in config: 187 | port = config['mysqld'].get("port", "3306") 188 | pid_file = config['mysqld'].get("pid-file") 189 | 190 | pid = Utils.get_pid(pid_file) 191 | if pid: 192 | ok, name = Utils.is_pid_running(pid) 193 | if ok and name == "mysqld": 194 | instances.append((self.local_ip, port, "")) 195 | 196 | return instances 197 | 198 | def gets(self): 199 | instances = self.get_from_ip_range() 200 | 201 | local_instance = self.get_local_listening_ports() 202 | if local_instance: 203 | instances.extend(local_instance) 204 | 205 | config_instances = self.get_from_config() 206 | if config_instances: 207 | instances.extend(config_instances) 208 | return Utils.deduplicate(instances) 209 | 210 | class Cache: 211 | 212 | def __init__(self, temp_file=None, duration=None): 213 | if not temp_file: 214 | self.temp_file = os.path.join(tempfile.gettempdir(), "auto_discover_mysql_result.json") 215 | else: 216 | self.temp_file = temp_file 217 | if duration and isinstance(duration, int) and duration > 3600: 218 | self.duration = duration 219 | else: 220 | self.duration = 3600 221 | 222 | @classmethod 223 | def convert_data(cls, data): 224 | data = { 225 | "create_at": int(time.time()), 226 | "results": data, 227 | } 228 | return data 229 | 230 | def out_date(self, data): 231 | if isinstance(data, dict) and time.time() - data.get("create_at", 0) > self.duration: 232 | return True 233 | return False 234 | 235 | def read(self): 236 | try: 237 | with open(self.temp_file, 'r') as temp_file: 238 | json_data = json.load(temp_file) 239 | except: 240 | json_data = {} 241 | return json_data 242 | 243 | def write(self, data): 244 | with open(self.temp_file, "w") as temp_file: 245 | json.dump(data, temp_file) 246 | 247 | 248 | class Utils: 249 | 250 | def __init__(self): 251 | pass 252 | 253 | @classmethod 254 | def parse_port_range(cls, port_range): 255 | ports = [] 256 | ranges = port_range.split(",") 257 | for r in ranges: 258 | if "-" in r: 259 | start, end = r.split("-") 260 | try: 261 | start = int(start) 262 | end = int(end) 263 | ports.extend(range(start, end + 1)) 264 | except ValueError: 265 | print("invalid port:", r) 266 | else: 267 | try: 268 | port = int(r) 269 | ports.append(port) 270 | except ValueError: 271 | print("invalid port:", r) 272 | return ports 273 | 274 | @classmethod 275 | def get_local_ip(cls): 276 | interfaces = psutil.net_if_addrs() 277 | for interface in interfaces: 278 | addresses = interfaces[interface] 279 | for address in addresses: 280 | if address.family == socket.AF_INET and \ 281 | not address.address.startswith('127.') and \ 282 | not address.address.startswith('172.'): 283 | return address.address 284 | return "" 285 | 286 | @classmethod 287 | def is_pid_running(cls, pid): 288 | if not pid: 289 | return False, "" 290 | try: 291 | process = psutil.Process(pid) 292 | return process.is_running(), process.name() 293 | except psutil.NoSuchProcess: 294 | return False, "" 295 | 296 | @classmethod 297 | def get_pid(cls, pid_file_path): 298 | try: 299 | with open(pid_file_path, "r") as file: 300 | pid = file.read().strip() 301 | return int(pid) 302 | except FileNotFoundError: 303 | return None 304 | except IOError as e: 305 | return None 306 | 307 | @classmethod 308 | def deduplicate(cls, data): 309 | unique_data = {} 310 | for item in data: 311 | key = (item[0], item[1]) 312 | if item[2] != "": 313 | unique_data[key] = item 314 | return list(unique_data.values()) 315 | 316 | @classmethod 317 | def convert_input(cls): 318 | if len(sys.argv) != 5: 319 | return 320 | 321 | c_cidrs = list(set(sys.argv[1].split(","))) 322 | 323 | c_ports = [3306] 324 | for item in sys.argv[2].split(","): 325 | try: 326 | c_ports.append(int(item)) 327 | except ValueError: 328 | continue 329 | c_ports = list(set(c_ports)) 330 | 331 | c_paths = list(set(sys.argv[3].split(","))) 332 | 333 | return c_cidrs, c_ports, c_paths, sys.argv[4] 334 | 335 | 336 | class AutoDiscovery(object): 337 | 338 | @property 339 | def unique_key(self): 340 | return "mysql_name" 341 | 342 | @staticmethod 343 | def attributes(): 344 | """ 345 | :return: 返回属性字段列表, 列表项是(名称, 类型, 描述), 名称必须是英文 346 | 类型: String Integer Float Date DateTime Time JSON 347 | """ 348 | return [ 349 | ("mysql_name", "String", "实例名称"), 350 | ("ip", "String", "ip"), 351 | ("port", "Integer", "端口"), 352 | ("version", "String", "版本信息") 353 | ] 354 | 355 | @staticmethod 356 | def run(): 357 | """ 358 | 执行入口, 返回采集的属性值 359 | :return: 返回一个列表, 列表项是字典, 字典key是属性名称, value是属性值 360 | 例如: 361 | return [dict(ci_type="server", private_ip="192.168.1.1")] 362 | """ 363 | Module().check_install_modules() 364 | data = Cache().read() 365 | if not data or Cache().out_date(data): 366 | all_instances = Scan(cidrs, global_ports_range, paths, g_scan_ip=global_scan_ip).gets() 367 | results = [] 368 | for instance in all_instances: 369 | ip, port, version = instance 370 | results.append(dict(mysql_name="{0}-{1}".format(ip, port), ip=ip, port=port, version=version)) 371 | data = Cache().convert_data(results) 372 | Cache().write(data) 373 | return data.get("results", []) 374 | 375 | 376 | if __name__ == "__main__": 377 | result = AutoDiscovery().run() 378 | if isinstance(result, list): 379 | print("AutoDiscovery::Result::{}".format(json.dumps(result))) 380 | else: 381 | print("ERROR: 采集返回必须是列表") 382 | -------------------------------------------------------------------------------- /docs/images/wechat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/ops-tools/c8912d0e51de78cfed487f57553ccf3f6b4d394e/docs/images/wechat.jpg -------------------------------------------------------------------------------- /docs/images/wechat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/ops-tools/c8912d0e51de78cfed487f57553ccf3f6b4d394e/docs/images/wechat.png -------------------------------------------------------------------------------- /messenger/README.md: -------------------------------------------------------------------------------- 1 | # Messenger 2 | 3 | Messenger已迁移至新的仓库👇 4 | 5 | **[https://github.com/veops/messenger](https://github.com/veops/messenger)** -------------------------------------------------------------------------------- /netTopology/.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | vendor/* 3 | -------------------------------------------------------------------------------- /netTopology/README.md: -------------------------------------------------------------------------------- 1 | ### 描述 2 | > 基于snmp自动发现网络拓扑结构,并生成网络拓扑数据结构,输出结果主要包含nodes,links,即节点、连线,并定时进行每个端口的流量、错误数、广播包 3 | > 丢包信息采集 4 | 5 | ### 运行 6 | ```shell 7 | go build . 8 | ./netTopology 9 | ``` 10 | 11 | ### 输出结果 12 | ```json 13 | { 14 | "nodes":[ 15 | { 16 | "name":"10.172.135.254", 17 | "alias":"", 18 | "type":"router", 19 | "x":0, 20 | "y":0, 21 | "ips":[ 22 | "10.172.135.254" 23 | ], 24 | "locals":[ 25 | "10.172.135.0/255.255.255.0", 26 | "10.172.135.255/255.255.255.255" 27 | ], 28 | "des_ips":{ 29 | "GigabitEthernet0/0/0":[ 30 | "10.172.135.1" 31 | ], 32 | "GigabitEthernet0/0/6":[ 33 | "58.33.167.73" 34 | ] 35 | } 36 | } 37 | ], 38 | "links":[ 39 | { 40 | "node_from":"GigabitEthernet0/0/1", 41 | "node_to":"", 42 | "node_from_name":"192.168.1.2", 43 | "node_to_name":"192.168.13", 44 | "value":0, 45 | "unit":"", 46 | "level":0, 47 | "if_index":"", 48 | "traffic_load":0, 49 | "port":"", 50 | "options":null 51 | } 52 | ], 53 | "location":false, 54 | "graph_id":"id1" 55 | } 56 | ``` 57 | 58 | ### 要做 59 | > 后面将把前端展示的代码,以及结合手动编辑架构结合的功能加入进来,下图是一张基于本代码生成的数据进行后续前端展示的效果图 60 | 61 | ![img.png](img.png) -------------------------------------------------------------------------------- /netTopology/discover/collector.go: -------------------------------------------------------------------------------- 1 | // Package discover /* 2 | package discover 3 | 4 | import ( 5 | "errors" 6 | "fmt" 7 | "math/big" 8 | "strings" 9 | "time" 10 | 11 | gsnmp "github.com/gosnmp/gosnmp" 12 | "github.com/patrickmn/go-cache" 13 | . "netTopology/internal" 14 | ) 15 | 16 | type PortDetail struct { 17 | Port string 18 | Index string 19 | Name string 20 | } 21 | 22 | type IfCollectorConfig struct { 23 | Oid string 24 | Alias string 25 | Order int 26 | Group string `json:"-"` 27 | } 28 | 29 | // PortsDetail map[string]map[string]PortDetail 30 | // map[ip]map[port]PortDetail 31 | var ( 32 | PortsDetail = cache.New(time.Minute, time.Minute) 33 | IfCollectors = map[string]IfCollectorConfig{ 34 | "net_if_in_octets": {OLibrary.IfHCInOctets, "输入带宽", 1, "带宽"}, 35 | "net_if_out_octets": {OLibrary.IfHCOutOctets, "输出带宽", 2, "带宽"}, 36 | //"net_if_in_ucast_pkts": {OLibrary.IfInUcastPkts, "输入非广播包数", 3, "非广播包数"}, 37 | //"net_if_out_ucast_pkts": {OLibrary.IfOutUcastPkts, "输出非广播包数", 4, "非广播包数"}, 38 | "net_if_in_nucast_pkts": {OLibrary.IfInNUcastPkts, "输入广播包数", 5, "广播包数"}, 39 | "net_if_out_nucast_pkts": {OLibrary.IfOutNUcastPkts, "输出广播包数", 6, "广播包数"}, 40 | "net_if_in_discards": {OLibrary.IfInDiscards, "输入包丢弃数", 7, "包丢弃数"}, 41 | "net_if_out_discards": {OLibrary.IfOutDiscards, "输出包丢弃数", 8, "包丢弃数"}, 42 | "net_if_in_errors": {OLibrary.IfInErrors, "输入包错误数", 9, "包错误数"}, 43 | "net_if_out_errors": {OLibrary.IfOutErrors, "输出包错误数", 10, "包错误数"}, 44 | "net_if_highspeed": {OLibrary.IfHighSpeed, "最大带宽", 11, "最大带宽"}, 45 | "net_if_oper_status": {OLibrary.IfOperStatus, "接口配置状态", 12, "接口配置状态"}, 46 | "net_if_admin_status": {OLibrary.IfOperStatus, "接口工作状态", 12, "接口工作状态"}, 47 | //ifAdminStatus是指配置状态。 48 | //ifOperStatus是指实际工作状态 49 | } 50 | ) 51 | 52 | func GetIfDetails(target string, snmpClient *gsnmp.GoSNMP) (data map[string]PortDetail, err error) { 53 | if v, ok := PortsDetail.Get(target); ok { 54 | data = v.(map[string]PortDetail) 55 | return 56 | } 57 | data = map[string]PortDetail{} 58 | if snmpClient == nil { 59 | snmpClient, err = NewSnmp(target) 60 | if err != nil { 61 | err = fmt.Errorf("new snmp client for %s failed %s", target, err.Error()) 62 | return 63 | } else { 64 | defer snmpClient.Conn.Close() 65 | } 66 | } 67 | 68 | res1, er := FetchItems("walk", target, []string{OLibrary.IfDes}, snmpClient) 69 | if er != nil { 70 | err = errors.New(StringConcat("fetch item failed for ", target, OLibrary.IfDes)) 71 | return 72 | } 73 | for _, v := range res1 { 74 | port := strings.TrimPrefix(v.Oid, fmt.Sprintf("%s.", OLibrary.IfDes)) 75 | data[port] = PortDetail{Name: v.DataValue.(string), Port: port} 76 | } 77 | res2, er := FetchItems("walk", target, []string{OLibrary.IfIndex}, snmpClient) 78 | if er != nil { 79 | err = errors.New(StringConcat("fetch item failed for ", target, OLibrary.IfDes)) 80 | return 81 | } 82 | for _, v := range res2 { 83 | port := strings.TrimPrefix(v.Oid, fmt.Sprintf("%s.", OLibrary.IfIndex)) 84 | if v1, ok := data[port]; ok { 85 | v1.Index = fmt.Sprintf("%s", v.DataValue) 86 | data[port] = v1 87 | } 88 | } 89 | PortsDetail.SetDefault(target, data) 90 | return 91 | } 92 | 93 | func ifStatistics(target string) (data map[string]map[string]int64, err error) { 94 | snmpClient, er := NewSnmp(target) 95 | if er != nil { 96 | e := fmt.Errorf("new snmp client for %s failed %s", target, er.Error()) 97 | return nil, e 98 | } else { 99 | defer snmpClient.Conn.Close() 100 | } 101 | 102 | data = map[string]map[string]int64{} 103 | port := "" 104 | for k, v := range IfCollectors { 105 | res, er := FetchItems("walk", target, []string{v.Oid}, snmpClient, 2) 106 | if er != nil { 107 | err = er 108 | return 109 | } 110 | data[k] = map[string]int64{} 111 | for _, v1 := range res { 112 | port = strings.TrimPrefix(v1.Oid, fmt.Sprintf("%s.", v.Oid)) 113 | data[k][port] = v1.DataValue.(*big.Int).Int64() 114 | } 115 | } 116 | return data, nil 117 | } 118 | -------------------------------------------------------------------------------- /netTopology/discover/entry.go: -------------------------------------------------------------------------------- 1 | package discover 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "time" 8 | 9 | "github.com/pkg/errors" 10 | "github.com/sirupsen/logrus" 11 | 12 | . "netTopology/internal" 13 | ) 14 | 15 | const ( 16 | RedisKeyDiscoverLinks = "Net::Discover::Links" 17 | RedisKeyCompleteLinks = "Net::Complete::Links" 18 | ) 19 | 20 | type CollectorConfig struct { 21 | Id string `json:"id"` 22 | Name string 23 | Gateway string `json:"gateway"` 24 | Nodes []*NodeSnmp 25 | } 26 | 27 | type Discover interface { 28 | Collectors(gateway string) ([]*CollectorConfig, error) 29 | Topology(config *CollectorConfig) (*TopologyGraph, error) 30 | UploadRecord(data *TopologyGraph) error 31 | UploadTsRecord(target string, data map[string]map[string]int64) 32 | Nodes() []string 33 | } 34 | 35 | func tidyGraph(graph *TopologyGraph, portMapIfIndex map[string]map[string]int64, 36 | portMapDes map[string]map[string]string) { 37 | r := map[string]StpInfo{} 38 | NodeLinks.Range(func(key, value any) bool { 39 | v := value.(StpInfo) 40 | r[key.(string)] = v 41 | return true 42 | }) 43 | var links []*Link 44 | for _, v := range r { 45 | nodeFrom := v.Port 46 | if v1, ok := portMapDes[v.AgentIp]; ok { 47 | if v2, ok := v1[v.Port]; ok { 48 | nodeFrom = v2 49 | } 50 | } else if v1, ok := portMapIfIndex[v.AgentIp]; ok { 51 | if v2, ok := v1[v.Port]; ok { 52 | nodeFrom = fmt.Sprintf("%v", v2) 53 | } 54 | } 55 | gh := &Link{ 56 | NodeFromName: v.AgentIp, 57 | NodeFrom: nodeFrom, 58 | NodeToName: v.DesignAddress, 59 | } 60 | if v1, ok := r[v.DesignAddress]; ok { 61 | if v2, _, er := IpIdentGet(v1.AgentIp, nil); er == nil { 62 | gh.NodeToName = v2 63 | } else { 64 | gh.NodeToName = v1.AgentIp 65 | } 66 | } else { 67 | logrus.Warn("unmatched mac address", v.DesignAddress) 68 | } 69 | links = append(links, gh) 70 | } 71 | graph.Links = links 72 | var ips []string 73 | for index, v := range graph.Nodes { 74 | if v1, ok := NodeInfo.Load(v.Name); ok { 75 | v2 := v1.(map[int64]*IfIndexInfo) 76 | d := map[string][]string{} 77 | for _, v3 := range v2 { 78 | ips = []string{} 79 | for _, v4 := range v3.ToIps { 80 | v4 = ToHardwareAddr(v4) 81 | if ip, ok := MacIps.Load(v4); ok { 82 | ips = append(ips, ip.([]string)...) 83 | } else { 84 | ips = append(ips, v4) 85 | } 86 | } 87 | d[v3.Des] = ips 88 | } 89 | graph.Nodes[index].DesIps = d 90 | } 91 | } 92 | } 93 | 94 | // 95 | //func upsertConfigGraph(data *models.ConfigGraphLink) { 96 | // data.LinkId = internal.MD5(strings.Join([]string{data.Name, data.DeviceInterface, data.To}, "-")) 97 | // data.Action = 3 98 | // count, er := data.Count(db.ConfigGraphLinkCol, bson.M{"link_id": data.LinkId, "graph_id": data.GraphId}) 99 | // if er != nil { 100 | // _ = level.Error(g.Logger).Log("module", "net", "msg", er.Error()) 101 | // return 102 | // } 103 | // if count == 0 { 104 | // _, er = data.Add(db.ConfigGraphLinkCol, data) 105 | // if er != nil { 106 | // _ = level.Error(g.Logger).Log("module", "net", "msg", er.Error()) 107 | // } 108 | // return 109 | // } 110 | // return 111 | //} 112 | // 113 | //func updateLinks(links []*Link, graphId string) { 114 | // for _, v := range links { 115 | // t := &models.ConfigGraphLink{ 116 | // LinkNode: &models.LinkNode{ 117 | // Name: v.NodeFromName, 118 | // DeviceInterface: v.NodeFrom, 119 | // }, 120 | // To: v.NodeToName, 121 | // GraphId: graphId, 122 | // } 123 | // upsertConfigGraph(t) 124 | // } 125 | //} 126 | 127 | func start(md Discover, ident string) error { 128 | logrus.Info("start discover network topology") 129 | collectors, er := md.Collectors(ident) 130 | if er != nil { 131 | return er 132 | } 133 | 134 | for _, v := range collectors { 135 | logrus.Info("start collect from ", v.Id) 136 | graph, er1 := md.Topology(v) 137 | if er1 != nil { 138 | er = errors.Wrap(er, er1.Error()) 139 | continue 140 | } 141 | er1 = md.UploadRecord(graph) 142 | if er != nil { 143 | er = errors.Wrap(er, er1.Error()) 144 | } 145 | } 146 | logrus.Info("end discover network topology") 147 | logrus.Info("the last auto discover topology graph is as follows") 148 | logrus.Info("---------------------------------------------------") 149 | TG.Range(func(key, value any) bool { 150 | v := value.(*TopologyGraph) 151 | s, _ := json.MarshalIndent(v, "", " ") 152 | logrus.Info(string(s)) 153 | return true 154 | }) 155 | logrus.Info("---------------------------------------------------") 156 | return nil 157 | } 158 | 159 | func NetworkMonitorInit(cancel <-chan os.Signal, md Discover) { 160 | go func() { 161 | //time.Sleep(time.Duration(rand.New(rand.NewSource(time.Now().Unix())).Intn(24)) * time.Hour) 162 | er := start(md, "") 163 | if er != nil { 164 | logrus.Error(er) 165 | } 166 | ticker := time.NewTicker(time.Hour * 24) 167 | for { 168 | select { 169 | case <-cancel: 170 | return 171 | case <-ticker.C: 172 | er := start(md, "") 173 | if er != nil { 174 | logrus.Error(er) 175 | } 176 | } 177 | } 178 | }() 179 | // 网络流量每分钟更新一次 180 | go func() { 181 | nodes := md.Nodes() 182 | for _, node := range nodes { 183 | go func(node string) { 184 | data, err := ifStatistics(node) 185 | if err != nil { 186 | logrus.Error(err) 187 | } 188 | md.UploadTsRecord(node, data) 189 | }(node) 190 | } 191 | //ticker := time.NewTicker(time.Second * 10) 192 | ticker := time.NewTicker(time.Minute) 193 | for { 194 | select { 195 | case <-cancel: 196 | return 197 | case <-ticker.C: 198 | nodes := md.Nodes() 199 | for _, node := range nodes { 200 | go func(node string) { 201 | data, err := ifStatistics(node) 202 | if err != nil { 203 | logrus.Error(err) 204 | } 205 | md.UploadTsRecord(node, data) 206 | }(node) 207 | } 208 | } 209 | } 210 | }() 211 | } 212 | 213 | // 214 | //func UniversalAction(action string, req string) (resp, nextAction string, err error) { 215 | // switch action { 216 | // case "Net.UploadRecord": 217 | // data := TopologyGraph{} 218 | // er := json.Unmarshal([]byte(req), &data) 219 | // if er != nil { 220 | // logrus.Error(er) 221 | // return 222 | // } 223 | // md := MasterDiscover("master") 224 | // er = md.UploadRecord(&data) 225 | // return "", "", md.UploadRecord(&data) 226 | // case "Net.Collectors": 227 | // data := map[string]string{} 228 | // md := MasterDiscover("master") 229 | // if ident, ok := data["ident"]; ok { 230 | // d, er := md.Collectors(ident) 231 | // if er == nil { 232 | // s, er := json.Marshal(d) 233 | // if er == nil { 234 | // return string(s), "", nil 235 | // } 236 | // } 237 | // if er != nil { 238 | // logrus.Error(er) 239 | // return 240 | // } 241 | // } 242 | // } 243 | // return "", "", nil 244 | //} 245 | -------------------------------------------------------------------------------- /netTopology/discover/layer2.go: -------------------------------------------------------------------------------- 1 | package discover 2 | 3 | import ( 4 | "fmt" 5 | "github.com/sirupsen/logrus" 6 | "math/big" 7 | "strconv" 8 | "strings" 9 | "sync" 10 | 11 | gsnmp "github.com/gosnmp/gosnmp" 12 | 13 | . "netTopology/internal" 14 | ) 15 | 16 | type IfIndexInfo struct { 17 | IfIndex int64 18 | // ToIps对接的所有IP 19 | ToIps []string 20 | // Neighbor 对接的IP 21 | Neighbor string 22 | Port string 23 | // Des 接口别名 24 | Des string 25 | } 26 | 27 | type Subnet struct { 28 | Name string `json:"name"` 29 | // Router 网关路由器 30 | Router string `json:"router"` 31 | // Routers 子网内的交换机 32 | Routers map[string]string `json:"routers"` 33 | // Switches 子网内的交换机 34 | Switches map[string]string `json:"switches"` 35 | // Hosts 子网内的主机 36 | Hosts map[string]string `json:"hosts"` 37 | } 38 | 39 | var ( 40 | IfIndexMapIpRecord = sync.Map{} 41 | NodeInfo = sync.Map{} 42 | NodeLinks = sync.Map{} 43 | commonLocker = sync.RWMutex{} 44 | //ifIndexPortMap = sync.Map{} 45 | ) 46 | 47 | type Subnets struct { 48 | sync.RWMutex 49 | M map[string]*Subnet 50 | } 51 | 52 | func (s *Subnets) Get(name string) *Subnet { 53 | s.RLock() 54 | defer s.RUnlock() 55 | return s.M[name] 56 | } 57 | 58 | func (s *Subnets) Add(fieldType NodeType, name, value string) { 59 | s.Lock() 60 | defer s.Unlock() 61 | if _, ok := s.M[name]; !ok { 62 | s.M[name] = &Subnet{ 63 | Name: name, 64 | Switches: map[string]string{}, 65 | Hosts: map[string]string{}, 66 | Routers: map[string]string{}, 67 | } 68 | } 69 | switch fieldType { 70 | case NTRouter: 71 | s.M[name].Router = value 72 | s.M[name].Routers[value] = "" 73 | case NTSwitch: 74 | s.M[name].Switches[value] = "" 75 | case NTServer: 76 | s.M[name].Hosts[value] = "" 77 | } 78 | } 79 | 80 | func (s *Subnets) Clear() { 81 | s.Lock() 82 | defer s.Unlock() 83 | s.M = map[string]*Subnet{} 84 | } 85 | 86 | func (s *Subnets) Range(f func(k, v any) bool) { 87 | s.RLock() 88 | defer s.RUnlock() 89 | for k, v := range s.M { 90 | if !f(k, v) { 91 | break 92 | } 93 | } 94 | } 95 | 96 | func ScanTotalSubnetsAndNeighbor(graph *TopologyGraph) (map[string]map[string]int64, map[string]map[string]string) { 97 | SubnetsVar.Clear() 98 | IfIndexMapIpRecord = sync.Map{} 99 | // step1 扫描出各个子网内的交换机、路由器,以及整个arp表 100 | { 101 | wg := sync.WaitGroup{} 102 | ch := make(chan struct{}, 1000) 103 | Routes.Range(func(key, value any) bool { 104 | for _, objs := range [][]string{value.(*Arp).Subnets, value.(*Arp).PeerIps} { 105 | for _, v := range objs { 106 | if _, ok := SubnetsScanned.LoadOrStore(v, struct{}{}); ok { 107 | continue 108 | } 109 | ips, er := CidrHosts(v) 110 | if er != nil { 111 | logrus.Warn(er) 112 | continue 113 | } 114 | for _, v1 := range ips { 115 | if !ValidInnerIp(v1) { 116 | logrus.Info("invalid intranet ips", v1) 117 | break 118 | } 119 | wg.Add(1) 120 | ch <- struct{}{} 121 | go func(ip, cidr string, wg *sync.WaitGroup) { 122 | defer func() { 123 | wg.Done() 124 | <-ch 125 | }() 126 | nt := GetNodeType(ip) 127 | SubnetsVar.Add(nt, cidr, ip) 128 | switch nt { 129 | case NTSwitch, NTRouter: 130 | _, _ = ScanArp(ip, nil) 131 | Topology3SyncWait.Add(1) 132 | L3Topology(ip, graph, false) 133 | Topology3SyncWait.Wait() 134 | } 135 | }(v1, v, &wg) 136 | } 137 | } 138 | } 139 | return true 140 | }) 141 | wg.Wait() 142 | } 143 | logrus.Debug("end scan arp......") 144 | 145 | // step2 针对子网的route,switch 获取接口映射 146 | logrus.Info("start scan links") 147 | portMapIfIndex := map[string]map[string]int64{} 148 | portMapDes := map[string]map[string]string{} 149 | var f1 = func(ip string) { 150 | ident, r, er := IfIndexMapIp(ip, nil) 151 | if er != nil || len(r) == 0 { 152 | return 153 | } 154 | portMapIfIndex[ident] = map[string]int64{} 155 | portMapDes[ident] = map[string]string{} 156 | for ifIndex, v1 := range r { 157 | portMapIfIndex[ident][v1.Port] = ifIndex 158 | portMapDes[ident][v1.Port] = v1.Des 159 | } 160 | NodeInfo.Store(ident, r) 161 | NodeLink(ident, nil) 162 | } 163 | { 164 | SubnetsVar.Range(func(k, v any) bool { 165 | for ip := range v.(*Subnet).Routers { 166 | f1(ip) 167 | } 168 | for ip := range v.(*Subnet).Switches { 169 | f1(ip) 170 | } 171 | return true 172 | }) 173 | } 174 | r := map[string]StpInfo{} 175 | NodeLinks.Range(func(key, value any) bool { 176 | v := value.(StpInfo) 177 | r[key.(string)] = v 178 | return true 179 | }) 180 | var links []*Link 181 | for _, v := range r { 182 | nodeFrom := v.Port 183 | if v1, ok := portMapDes[v.AgentIp]; ok { 184 | if v2, ok := v1[v.Port]; ok { 185 | nodeFrom = v2 186 | } 187 | } else if v1, ok := portMapIfIndex[v.AgentIp]; ok { 188 | if v2, ok := v1[v.Port]; ok { 189 | nodeFrom = fmt.Sprintf("%v", v2) 190 | } 191 | } 192 | gh := &Link{ 193 | NodeFromName: v.AgentIp, 194 | NodeFrom: nodeFrom, 195 | NodeToName: v.DesignAddress, 196 | } 197 | if v1, ok := r[v.DesignAddress]; ok { 198 | if v2, _, er := IpIdentGet(v1.AgentIp, nil); er == nil { 199 | gh.NodeToName = v2 200 | } else { 201 | gh.NodeToName = v1.AgentIp 202 | } 203 | } else { 204 | logrus.Warn("unmatched mac address", v.DesignAddress) 205 | } 206 | links = append(links, gh) 207 | } 208 | graph.Links = links 209 | //// mac to ips 210 | //for i, v := range graph.Nodes { 211 | // for i1, v1 := range v.DesIps { 212 | // ips := []string{} 213 | // for _, v2 := range v1 { 214 | // if v3, ok := MacIps.Load(v2); ok { 215 | // ips = append(ips, v3.([]string)...) 216 | // } 217 | // } 218 | // ips = utils.RmDupElement(ips) 219 | // sort.Strings(ips) 220 | // graph.Nodes[i].DesIps[i1] = ips 221 | // fmt.Println("ips:", ips) 222 | // } 223 | //} 224 | return portMapIfIndex, portMapDes 225 | } 226 | 227 | func ifIndexMapIpSwitch(node string, data map[int64]*IfIndexInfo, snmp *gsnmp.GoSNMP) { 228 | macIfIndex := map[string]int64{} 229 | portIfIndex := map[string]int64{} 230 | ifIndexPort := map[int64]string{} 231 | ifIndexDes := map[int64]string{} 232 | // 物理端口对应接口 233 | r0, er0 := FetchItems("walk", node, []string{OLibrary.Dot1dBasePortIfIndex}, snmp) 234 | 235 | if er0 == nil { 236 | // mac对应物理端口 237 | r1, er1 := FetchItems("walk", node, []string{OLibrary.Dot1dTpFdbPort}, snmp) 238 | if er1 == nil { 239 | 240 | for _, v := range r0 { 241 | tmp := strings.Split(v.Oid, ".") 242 | p := tmp[len(tmp)-1] 243 | portIfIndex[p] = v.DataValue.(*big.Int).Int64() 244 | ifIndexPort[v.DataValue.(*big.Int).Int64()] = p 245 | } 246 | for _, v := range r1 { 247 | macIfIndex[strings.ReplaceAll(strings.TrimPrefix(v.Oid, OLibrary.Dot1dTpFdbPort+"."), ".", " ")] = 248 | portIfIndex[fmt.Sprintf("%v", v.DataValue)] 249 | } 250 | } 251 | } 252 | r2, er2 := FetchItems("walk", node, []string{OLibrary.IfDes}, snmp) 253 | if er2 == nil { 254 | for _, v := range r2 { 255 | index, er := strconv.ParseInt(strings.TrimPrefix(v.Oid, OLibrary.IfDes+"."), 10, 64) 256 | if er == nil { 257 | ifIndexDes[index] = fmt.Sprintf("%v", v.DataValue) 258 | } 259 | } 260 | } 261 | for index, port := range ifIndexPort { 262 | data[index] = &IfIndexInfo{ 263 | IfIndex: index, 264 | Port: port, 265 | Des: ifIndexDes[index], 266 | } 267 | } 268 | for k, v := range macIfIndex { 269 | if _, ok := data[v]; ok { 270 | data[v].ToIps = append(data[v].ToIps, k) 271 | } 272 | } 273 | } 274 | 275 | func ifIndexMapIpRoute(node string, data map[int64]*IfIndexInfo, snmp *gsnmp.GoSNMP) { 276 | var ( 277 | types = map[string]int64{} 278 | nextHops = map[string]string{} 279 | ifIndexDes = map[int64]string{} 280 | ) 281 | r, er := FetchItems("walk", node, []string{OLibrary.IfDes}, snmp) 282 | if er == nil { 283 | for _, v := range r { 284 | index, er := strconv.ParseInt(strings.TrimPrefix(v.Oid, OLibrary.IfDes+"."), 10, 64) 285 | if er == nil { 286 | ifIndexDes[index] = fmt.Sprintf("%v", v.DataValue) 287 | } 288 | } 289 | } 290 | r0, er0 := FetchItems("walk", node, []string{OLibrary.IpRouteType}, snmp) 291 | if er0 == nil { 292 | r1, er1 := FetchItems("walk", node, []string{OLibrary.IpRouteNextHop}, snmp) 293 | if er1 == nil { 294 | r2, er2 := FetchItems("walk", node, []string{OLibrary.IpRouteIfIndex}, snmp) 295 | if er2 == nil { 296 | for _, v := range r0 { 297 | types[strings.TrimPrefix(v.Oid, OLibrary.IpRouteType)] = v.DataValue.(*big.Int).Int64() 298 | } 299 | for _, v := range r1 { 300 | nextHops[strings.TrimPrefix(v.Oid, OLibrary.IpRouteNextHop)] = v.DataValue.(string) 301 | } 302 | for _, v := range r2 { 303 | k := strings.TrimPrefix(v.Oid, OLibrary.IpRouteIfIndex) 304 | ifIndex := v.DataValue.(*big.Int).Int64() 305 | if v1, ok := types[k]; ok && v1 == 4 { 306 | if _, ok1 := data[ifIndex]; ok1 { 307 | if !StringInSlice(nextHops[k], data[ifIndex].ToIps) { 308 | data[ifIndex].ToIps = append(data[ifIndex].ToIps, nextHops[k]) 309 | } 310 | } else { 311 | data[ifIndex] = &IfIndexInfo{ 312 | IfIndex: ifIndex, 313 | ToIps: []string{nextHops[k]}, 314 | Port: fmt.Sprintf("%v", ifIndex), 315 | Des: ifIndexDes[ifIndex], 316 | } 317 | } 318 | } 319 | } 320 | } 321 | } 322 | 323 | } 324 | } 325 | 326 | func IfIndexMapIp(node string, snmp *gsnmp.GoSNMP) (ident string, data map[int64]*IfIndexInfo, err error) { 327 | data = map[int64]*IfIndexInfo{} 328 | if snmp == nil { 329 | snmp, er := NewSnmp(node) 330 | if er != nil { 331 | err = er 332 | return 333 | } else { 334 | defer snmp.Conn.Close() 335 | } 336 | } 337 | ident, _, err = IpIdentGet(node, snmp) 338 | if err != nil { 339 | return 340 | } 341 | if _, ok := IfIndexMapIpRecord.LoadOrStore(ident, struct{}{}); ok { 342 | return ident, data, nil 343 | } 344 | // 对于交换机 345 | ifIndexMapIpSwitch(ident, data, snmp) 346 | // 对于有路由的情况 347 | if len(data) == 0 { 348 | ifIndexMapIpRoute(ident, data, snmp) 349 | } 350 | for k := range data { 351 | data[k].ToIps = RmDupElement(data[k].ToIps) 352 | for _, v1 := range data[k].ToIps { 353 | if v2, ok := IpNodeTypeMaps.Get(v1); ok { 354 | switch v2.(NodeType) { 355 | case NTSwitch, NTRouter: 356 | if ip, _, er := IpIdentGet(v1, nil); er == nil && ip != "" && ip != node { 357 | data[k].Neighbor = ip 358 | goto Loop 359 | } 360 | } 361 | } 362 | } 363 | Loop: 364 | continue 365 | } 366 | return 367 | } 368 | 369 | // ScanArp 获取mac,ip的映射关系 370 | func ScanArp(ipAddr string, snmp *gsnmp.GoSNMP) (macIpsMap map[string][]string, er error) { 371 | if v, ok := IpIdent.Get(ipAddr); ok { 372 | ipAddr = v.(string) 373 | } 374 | if _, ok := ScanArpFinished.LoadOrStore(ipAddr, false); ok { 375 | return 376 | } 377 | if snmp == nil { 378 | snmp, er = NewSnmp(ipAddr) 379 | if er != nil { 380 | return 381 | } 382 | } 383 | arpSnmp, err := FetchItems("walk", ipAddr, []string{OLibrary.IpNetToMediaPhysAddress}, snmp) 384 | if err != nil { 385 | er = err 386 | return 387 | } 388 | r1, err := FetchItems("walk", ipAddr, []string{OLibrary.Dot1dBaseBridgeAddress}, snmp) 389 | if err != nil { 390 | return 391 | } 392 | macIpsMap = make(map[string][]string) 393 | //macIpsMap = make(map[interface{}][]string) 394 | 395 | for _, v := range arpSnmp { 396 | tmp := strings.Split(v.Oid, ".") 397 | if len(tmp) < 5 { 398 | continue 399 | } 400 | //h := HardwareAddr(v.Raw) 401 | h := ToHardwareAddr(v.Raw) 402 | if h == "" { 403 | continue 404 | } 405 | macIpsMap[h] = append(macIpsMap[h], strings.Join(tmp[len(tmp)-4:], ".")) 406 | //if _, ok := macIpsMap[h]; ok { 407 | // macIpsMap[h] = append(macIpsMap[h], strings.Join(tmp[len(tmp)-4:], ".")) 408 | //} else { 409 | // macIpsMap[h] = []string{strings.Join(tmp[len(tmp)-4:], ".")} 410 | //} 411 | } 412 | for _, v := range r1 { 413 | //h := HardwareAddr(v.Raw) 414 | h := ToHardwareAddr(v.Raw) 415 | macIpsMap[h] = append(macIpsMap[h], ipAddr) 416 | //if _, ok := macIpsMap[h]; ok { 417 | // macIpsMap[h] = append(macIpsMap[h], ipAddr) 418 | //} else { 419 | // macIpsMap[h] = []string{ipAddr} 420 | //} 421 | } 422 | 423 | for k, v := range macIpsMap { 424 | MacIpsSet(k, v) 425 | for _, i := range v { 426 | //TotalIps.Store(i, struct{}{}) 427 | IpMacAddress.Store(i, k) 428 | } 429 | } 430 | return 431 | } 432 | 433 | func MacIpsSet(k, v any) { 434 | commonLocker.Lock() 435 | defer commonLocker.Unlock() 436 | var tmp []string 437 | if v1, ok := MacIps.Load(k); ok { 438 | tmp = v1.([]string) 439 | tmp = append(tmp, v.([]string)...) 440 | } else { 441 | tmp = v.([]string) 442 | } 443 | MacIps.Store(k, RmDupElement(tmp)) 444 | 445 | } 446 | 447 | type StpInfo struct { 448 | AgentIp string 449 | AgentAddress string 450 | Port string 451 | PortState int64 452 | DesignAddress string 453 | DesignPort string 454 | } 455 | 456 | func NodeLink(node string, snmp *gsnmp.GoSNMP) { 457 | if _, ok := NodeLinkFinished.LoadOrStore(node, false); ok { 458 | return 459 | } 460 | 461 | r1, err := FetchItems("walk", node, []string{OLibrary.Dot1dBaseBridgeAddress}, snmp) 462 | if err != nil { 463 | return 464 | } 465 | r2, err := FetchItems("walk", node, []string{OLibrary.Dot1dStpPortDesignatedBridge}, snmp) 466 | if err != nil { 467 | return 468 | } 469 | r3, err := FetchItems("walk", node, []string{OLibrary.Dot1dStpPortState}, snmp) 470 | if err != nil { 471 | return 472 | } 473 | r4, err := FetchItems("walk", node, []string{OLibrary.Dot1dStpPort}, snmp) 474 | if err != nil { 475 | return 476 | } 477 | r5, err := FetchItems("walk", node, []string{OLibrary.Dot1dStpPortDesignatedPort}, snmp) 478 | if err != nil { 479 | return 480 | } 481 | 482 | selfMacAddress := "" 483 | for _, v := range r1 { 484 | //selfMacAddress = HardwareAddr(v.Raw) 485 | selfMacAddress = ToHardwareAddr(v.Raw) 486 | } 487 | var states = map[string]int64{} 488 | for _, v := range r3 { 489 | states[strings.TrimPrefix(v.Oid, OLibrary.Dot1dStpPortState)] = v.DataValue.(*big.Int).Int64() 490 | } 491 | var Ports = map[string]string{} 492 | for _, v := range r4 { 493 | Ports[strings.TrimPrefix(v.Oid, OLibrary.Dot1dStpPort)] = fmt.Sprintf("%v", v.Raw) 494 | } 495 | var dPorts = map[string]string{} 496 | for _, v := range r5 { 497 | dPorts[strings.TrimPrefix(v.Oid, OLibrary.Dot1dStpPortDesignatedPort)] = fmt.Sprintf("%v", v.Raw) 498 | } 499 | 500 | for _, v := range r2 { 501 | if ToHardwareAddr(v.Raw)[6:] == selfMacAddress { 502 | continue 503 | } 504 | index := strings.TrimPrefix(v.Oid, OLibrary.Dot1dStpPortDesignatedBridge) 505 | 506 | if v1, ok := states[strings.TrimPrefix(v.Oid, OLibrary.Dot1dStpPortDesignatedBridge)]; ok && v1 == 5 { 507 | data := StpInfo{ 508 | AgentIp: node, 509 | AgentAddress: selfMacAddress, 510 | Port: Ports[index], 511 | PortState: states[index], 512 | DesignAddress: ToHardwareAddr(v.Raw)[6:], 513 | DesignPort: dPorts[index], 514 | } 515 | NodeLinks.Store(selfMacAddress, data) 516 | //fmt.Println("store:::", node, v.Raw, HardwareAddr(v.Raw)[6:], selfMacAddress) 517 | } 518 | break 519 | } 520 | } 521 | -------------------------------------------------------------------------------- /netTopology/discover/layer3.go: -------------------------------------------------------------------------------- 1 | package discover 2 | 3 | import ( 4 | "errors" 5 | "github.com/sirupsen/logrus" 6 | "math/big" 7 | "sort" 8 | //"sort" 9 | "strings" 10 | //"time" 11 | 12 | gsnmp "github.com/gosnmp/gosnmp" 13 | 14 | . "netTopology/internal" 15 | ) 16 | 17 | func Layer3IpLinks(target string, snmpClient *gsnmp.GoSNMP) (horizontal, vertical map[string]int64, err error) { 18 | horizontal = map[string]int64{} 19 | vertical = map[string]int64{} 20 | routeType, er := FetchItems("walk", target, []string{OLibrary.IpRouteType}, snmpClient) 21 | if er != nil { 22 | err = errors.New(StringConcat("fetch item failed for ", target, OLibrary.IpRouteType)) 23 | return 24 | } 25 | routeNextHop, er := FetchItems("walk", target, []string{OLibrary.IpRouteNextHop}, snmpClient) 26 | if er != nil { 27 | err = errors.New(StringConcat("fetch item failed for ", target, OLibrary.IpRouteNextHop)) 28 | return 29 | } 30 | routeIfIndex, er := FetchItems("walk", target, []string{OLibrary.IpRouteIfIndex}, snmpClient) 31 | if er != nil { 32 | err = errors.New(StringConcat("fetch item failed for ", target, OLibrary.IpRouteIfIndex)) 33 | return 34 | } 35 | nextHop := map[string]string{} 36 | for _, v := range routeNextHop { 37 | nextHop[strings.TrimPrefix(v.Oid, OLibrary.IpRouteNextHop+".")] = v.DataValue.(string) 38 | } 39 | ifIndex := map[string]int64{} 40 | for _, v := range routeIfIndex { 41 | ifIndex[strings.TrimPrefix(v.Oid, OLibrary.IpRouteIfIndex+".")] = v.DataValue.(*big.Int).Int64() 42 | } 43 | var key string 44 | for _, v := range routeType { 45 | key = strings.TrimPrefix(v.Oid, OLibrary.IpRouteType+".") 46 | // 有时候下一跳指向本地地址,或者防火墙virtual-if*; virtual-if0为根防火墙 47 | if v1, ok := IpIdent.Get(nextHop[key]); ok && v1.(string) == target { 48 | continue 49 | } 50 | if v.DataValue.(*big.Int).Int64() == 3 { 51 | vertical[key] = ifIndex[key] 52 | } else if v.DataValue.(*big.Int).Int64() == 4 { 53 | horizontal[key] = ifIndex[key] 54 | } 55 | } 56 | 57 | newHorizontal := map[string]int64{} 58 | newVertical := map[string]int64{} 59 | 60 | for k, v := range vertical { 61 | v1 := Final(k, nextHop) 62 | if v1 != k && v1 != "127.0.0.1" && v1 != "127.0.0.0" { 63 | newVertical[v1] = v 64 | } 65 | } 66 | 67 | for k, v := range horizontal { 68 | v1 := Final(k, nextHop) 69 | if v1 != k && v1 != "127.0.0.1" && v1 != "127.0.0.0" { 70 | newHorizontal[v1] = v 71 | } 72 | } 73 | vertical = newVertical 74 | horizontal = newHorizontal 75 | return 76 | } 77 | 78 | // L3Topology 79 | // 访问当前路由器的路由表,对每个路由表项 80 | // ipRouteType等于 indirect(4),将路由表中ipRouteNextHop的内容不重复地加入邻居列表中 81 | // ipRouteType等于 direct(3),把ipRouteDest和 ipRouteMask不重复地放到本地相连子网队列中。 82 | func L3Topology(target string, graph *TopologyGraph, recursive bool) { 83 | defer Topology3SyncWait.Done() 84 | if !ValidInnerIp(target) { 85 | logrus.Warnf("invalid intranet ip for %s", target) 86 | return 87 | } 88 | if _, ok := L3Nodes.Load(target); ok { 89 | return 90 | } 91 | if v, ok := IpIdent.Get(target); ok { 92 | target = v.(string) 93 | } 94 | snmpClient, er := NewSnmp(target) 95 | if er != nil { 96 | logrus.Infof("New snmp client for %s failed %s", target, er.Error()) 97 | return 98 | } else { 99 | defer snmpClient.Conn.Close() 100 | } 101 | var ( 102 | localIps, peerIps []string 103 | ) 104 | arp := DiscoverIps(target, snmpClient) 105 | if arp == nil || arp.Target == "" { 106 | logrus.Info("get empty ip for ", target) 107 | return 108 | } 109 | target = arp.Target 110 | IpEntOfDevice.Store(target, arp.SelfIps) 111 | _ = peerIps 112 | if target == "" { 113 | logrus.Info("get self ip failed for ", target) 114 | return 115 | } 116 | 117 | horizontal, vertical, er := Layer3IpLinks(target, snmpClient) 118 | if er != nil { 119 | logrus.Warnf("Layer3IpLinks %s failed: %v", target, er) 120 | return 121 | } 122 | for k := range vertical { 123 | localIps = append(localIps, k) 124 | } 125 | { 126 | if _, ok := L3Nodes.LoadOrStore(target, false); !ok { 127 | topology3Lock.Lock() 128 | graph.Nodes = append(graph.Nodes, &Node{ 129 | Name: target, 130 | Type: GetNodeType(target).String(), 131 | Locals: arp.Subnets, 132 | Ips: arp.SelfIps, 133 | }) 134 | topology3Lock.Unlock() 135 | } 136 | } 137 | for ip, ifIndex := range horizontal { 138 | Topology3SyncWait.Add(1) 139 | go func(ip string, ifIndex int64) { 140 | snmpCli, er := NewSnmp(ip) 141 | if er != nil { 142 | logrus.Infof("New snmp client[%s] failed %s", ip, er.Error()) 143 | Topology3SyncWait.Done() 144 | return 145 | } else { 146 | defer snmpCli.Conn.Close() 147 | } 148 | tmpArp := DiscoverIps(ip, snmpCli) 149 | if tmpArp.Target == "" { 150 | Topology3SyncWait.Done() 151 | return 152 | } 153 | if _, ok := L3Nodes.LoadOrStore(tmpArp.Target, false); !ok { 154 | topology3Lock.Lock() 155 | graph.Nodes = append(graph.Nodes, &Node{ 156 | Name: tmpArp.Target, 157 | Type: GetNodeType(ip).String(), 158 | Locals: tmpArp.Subnets, 159 | Ips: tmpArp.SelfIps, 160 | }) 161 | topology3Lock.Unlock() 162 | if recursive { 163 | L3Topology(tmpArp.Target, graph, recursive) 164 | } else { 165 | Topology3SyncWait.Done() 166 | } 167 | } else { 168 | Topology3SyncWait.Done() 169 | } 170 | }(ip, ifIndex) 171 | } 172 | } 173 | 174 | // SubnetIps 获取本机IP、连接的子网 175 | func SubnetIps(target string, snmpClient *gsnmp.GoSNMP) (subnetIps, subnetMask []string, er error) { 176 | r1, err := FetchItems("walk", target, []string{OLibrary.IpAdEntAddr}, snmpClient) 177 | if err != nil { 178 | er = err 179 | return 180 | } 181 | r2, err := FetchItems("walk", target, []string{OLibrary.IpAdEntNetMask}, snmpClient) 182 | if err != nil { 183 | er = err 184 | return 185 | } 186 | var ( 187 | ips map[string]string 188 | obj string 189 | ) 190 | for _, v := range r1 { 191 | obj = strings.TrimPrefix(v.Oid, OLibrary.IpAdEntAddr) 192 | ips[obj] = v.DataValue.(string) 193 | } 194 | 195 | for _, v := range r2 { 196 | obj = strings.TrimPrefix(v.Oid, OLibrary.IpAdEntNetMask) 197 | if ips[obj] != "" && ips[obj] != "127.0.0.1" && ips[obj] != "127.0.0.0" { 198 | subnetIps = append(subnetIps, ips[obj]) 199 | subnetMask = append(subnetMask, v.DataValue.(string)) 200 | } 201 | } 202 | return 203 | } 204 | 205 | // DiscoverIps 发现网络IP以及自身IP 206 | // localIps 本机ip列表; peerIps 对端ip列表 207 | func DiscoverIps(target string, snmpClient *gsnmp.GoSNMP) (result *Arp) { 208 | logrus.Info("DiscoverIps for ARP ", target) 209 | result = &Arp{} 210 | if v, ok := DiscoverIpsCache.Get(target); ok { 211 | return v.(*Arp) 212 | } 213 | defer func() { 214 | logrus.Info("DiscoverIps for ARP end: ", target) 215 | DiscoverIpsCache.SetDefault(target, result) 216 | }() 217 | r0, er := FetchItems("walk", target, []string{OLibrary.IpAdEntAddr}, snmpClient) 218 | if er != nil { 219 | logrus.Warnf("fetch %s %s failed %v", target, OLibrary.IpAdEntAddr, er) 220 | return 221 | } 222 | //r1, er := FetchItems("walk", target, []string{OLibrary.IpNetToMediaNetAddress}, snmpClient) 223 | //if er != nil { 224 | // fmt.Println(er) 225 | // return 226 | //} 227 | //r2, er := FetchItems("walk", target, []string{OLibrary.IpNetToMediaType}, snmpClient) 228 | //if er != nil { 229 | // fmt.Println(er) 230 | // return 231 | //} 232 | r3, er := FetchItems("walk", target, []string{OLibrary.IpRouteDest}, snmpClient) 233 | if er != nil { 234 | logrus.Warnf("fetch %s %s failed %v", target, OLibrary.IpRouteDest, er) 235 | return 236 | } 237 | r4, er := FetchItems("walk", target, []string{OLibrary.IpAdEntNetMask}, snmpClient) 238 | if er != nil { 239 | logrus.Warnf("fetch %s %s failed %v", target, OLibrary.IpAdEntNetMask, er) 240 | return 241 | } 242 | r5, er := FetchItems("walk", target, []string{OLibrary.IpRouteType}, snmpClient) 243 | if er != nil { 244 | logrus.Warnf("fetch %s %s failed %v", target, OLibrary.IpRouteType, er) 245 | return 246 | } 247 | r6, er := FetchItems("walk", target, []string{OLibrary.IpRouteMask}, snmpClient) 248 | if er != nil { 249 | logrus.Warnf("fetch %s %s failed %v", target, OLibrary.IpRouteMask, er) 250 | return 251 | } 252 | var ( 253 | //ips = map[string]string{} 254 | masks = map[string]string{} 255 | //obj string 256 | ) 257 | { 258 | for _, v := range r4 { 259 | masks[strings.TrimPrefix(v.Oid, OLibrary.IpAdEntNetMask)] = v.DataValue.(string) 260 | } 261 | sort.Slice(r0, func(i, j int) bool { 262 | return InetAtoi(r0[i].DataValue.(string)) < InetAtoi(r0[j].DataValue.(string)) 263 | }) 264 | for _, v := range r0 { 265 | if v.DataValue.(string) == "127.0.0.1" || 266 | v.DataValue.(string) == "0.0.0.0" || 267 | v.DataValue.(string) == "127.0.0.0" { 268 | continue 269 | } 270 | //ip := StringConcat(v.DataValue.(string), "/", masks[strings.TrimPrefix(v.Oid, OLibrary.IpAdEntAddr)]) 271 | result.SelfIps = append(result.SelfIps, v.DataValue.(string)) 272 | //if !StringInSlice(ip, result.Subnets) { 273 | // result.Subnets = append(result.Subnets, ip) 274 | //} 275 | } 276 | for _, v := range result.SelfIps { 277 | v = strings.Split(v, "/")[0] 278 | if _, er := NewSnmp(v); er == nil { 279 | result.Target = v 280 | break 281 | } else if PingHost(v) { 282 | result.Target = v 283 | continue 284 | } 285 | } 286 | for _, v := range result.SelfIps { 287 | v = strings.Split(v, "/")[0] 288 | IpIdent.SetDefault(v, result.Target) 289 | } 290 | } 291 | //{ 292 | // for _, v := range r1 { 293 | // obj = strings.TrimPrefix(v.Oid, OLibrary.IpNetToMediaNetAddress) 294 | // ips[obj] = v.DataValue.(string) 295 | // } 296 | // for _, v := range r2 { 297 | // obj = strings.TrimPrefix(v.Oid, OLibrary.IpNetToMediaType) 298 | // switch v.DataValue.(*big.Int).String() { 299 | // case "3": 300 | // result.PeerIps = append(result.PeerIps, ips[obj]) 301 | // case "4", "1": 302 | // if ips[obj] != "" && ips[obj] != "127.0.0.1" && ips[obj] != "127.0.0.0" { 303 | // ip := StringConcat(ips[obj], "/", masks["."+strings.SplitN(obj, ".", 3)[2]]) 304 | // if !StringInSlice(ip, result.Subnets) { 305 | // result.Subnets = append(result.Subnets, ip) 306 | // } 307 | // } 308 | // } 309 | // } 310 | //} 311 | { 312 | dest := map[string]string{} 313 | routeType := map[string]int64{} 314 | for _, v := range r3 { 315 | dest[strings.TrimPrefix(v.Oid, OLibrary.IpRouteDest)] = v.DataValue.(string) 316 | } 317 | for _, v := range r5 { 318 | routeType[strings.TrimPrefix(v.Oid, OLibrary.IpRouteType)] = v.DataValue.(*big.Int).Int64() 319 | } 320 | for _, v := range r6 { 321 | key := strings.TrimPrefix(v.Oid, OLibrary.IpRouteMask) 322 | if v1, ok := routeType[key]; ok { 323 | if v1 == int64(3) { 324 | if !strings.HasPrefix(dest[key], "127.") && 325 | dest[key] != "0.0.0.0" && 326 | !StringInSlice(dest[key], result.SelfIps) { 327 | result.Subnets = append(result.Subnets, StringConcat(dest[key], "/", v.DataValue.(string))) 328 | } 329 | } else if v1 == int64(4) { 330 | result.PeerIps = append(result.PeerIps, StringConcat(dest[key], "/", v.DataValue.(string))) 331 | } 332 | } 333 | } 334 | } 335 | Routes.Store(result.Target, result) 336 | return 337 | } 338 | -------------------------------------------------------------------------------- /netTopology/discover/master.go: -------------------------------------------------------------------------------- 1 | // Package discover /* 2 | package discover 3 | 4 | import ( 5 | "github.com/sirupsen/logrus" 6 | "math" 7 | "sort" 8 | "time" 9 | 10 | "github.com/jackpal/gateway" 11 | "github.com/shopspring/decimal" 12 | 13 | . "netTopology/internal" 14 | ) 15 | 16 | type MasterDiscover string 17 | 18 | func init() { 19 | go consumerMetrics() 20 | } 21 | 22 | // Collectors agents for collect and community config 23 | func (h *MasterDiscover) Collectors(ident string) (data []*CollectorConfig, er error) { 24 | var nodes []*NodeSnmp 25 | nodes = append(nodes, &NodeSnmp{"", "public", "2c"}) 26 | data = append(data, &CollectorConfig{ 27 | Id: "id1", 28 | Gateway: "", 29 | Nodes: nodes, 30 | Name: "test", 31 | }) 32 | return data, nil 33 | //var resp []*GraphSnmp 34 | //q := map[string]interface{}{"enable": true} 35 | //if ident != "" { 36 | // q["one_agent"] = ident 37 | //} 38 | //_, er = (&GraphSnmp{}).List(db.SnmpTopologyCol, q, int64(1), int64(1000), int64(1000), &resp) 39 | //if er != nil { 40 | // return 41 | //} 42 | //for _, v := range resp { 43 | // var nodes []*NodeSnmp 44 | // for _, v1 := range v.Nodes { 45 | // nodes = append(nodes, &NodeSnmp{v1.Ip, v1.Community, v1.Version}) 46 | // } 47 | // data = append(data, &CollectorConfig{ 48 | // Id: v.Id.Hex(), 49 | // Gateway: v.Gateway, 50 | // Nodes: nodes, 51 | // Name: v.Name, 52 | // }) 53 | //} 54 | //return 55 | } 56 | 57 | // UploadRecord save graph on disk 58 | func (h *MasterDiscover) UploadRecord(graph *TopologyGraph) error { 59 | //r, er := json.Marshal(graph) 60 | //if er != nil { 61 | // return er 62 | //} else { 63 | // _, err := utils.RedisAction("HSET", RedisKeyDiscoverLinks, graph.GraphId, r) 64 | // if err != nil { 65 | // return err 66 | // } 67 | //} 68 | //// 69 | //updateLinks(graph.Links, graph.GraphId) 70 | TG.Store(graph.GraphId, graph) 71 | return nil 72 | } 73 | 74 | func (h *MasterDiscover) Topology(config *CollectorConfig) (*TopologyGraph, error) { 75 | Init() 76 | graph := &TopologyGraph{GraphId: config.Id} 77 | 78 | if config.Gateway == "" { 79 | myGateway, er := gateway.DiscoverGateway() 80 | if er != nil { 81 | return graph, nil 82 | } 83 | config.Gateway = myGateway.String() 84 | } 85 | 86 | Topology3SyncWait.Add(1) 87 | for _, v := range config.Nodes { 88 | CommunityMap.SetDefault(v.Ip, v.Community) 89 | SnmpVersionMap.SetDefault(v.Ip, v.Version) 90 | } 91 | graph.Name = config.Name 92 | L3Topology(config.Gateway, graph, true) 93 | Topology3SyncWait.Wait() 94 | portMapIfIndex, portMapDes := ScanTotalSubnetsAndNeighbor(graph) 95 | tidyGraph(graph, portMapIfIndex, portMapDes) 96 | return graph, nil 97 | } 98 | 99 | func (h *MasterDiscover) Nodes() []string { 100 | //var ( 101 | // res []string 102 | // res1 = map[string]string{} 103 | // res2 = map[string]string{} 104 | //) 105 | //err := utils.ScanMap(RedisKeyCompleteLinks, res1) 106 | //if err != nil { 107 | // return res 108 | //} 109 | //nodes := map[string]struct{}{} 110 | //for _, v := range res1 { 111 | // data1 := TopologyGraphV1{} 112 | // er := json.Unmarshal([]byte(v), &data1) 113 | // if er != nil { 114 | // _ = level.Warn(g.Logger).Log("module", "net", "msg", er.Error()) 115 | // continue 116 | // } 117 | // for _, v1 := range data1.Nodes { 118 | // nodes[v1.Name] = struct{}{} 119 | // } 120 | //} 121 | //err = utils.ScanMap(RedisKeyDiscoverLinks, res2) 122 | //if err != nil { 123 | // return res 124 | //} 125 | //for _, v := range res2 { 126 | // data2 := TopologyGraph{} 127 | // er := json.Unmarshal([]byte(v), &data2) 128 | // if er != nil { 129 | // _ = level.Warn(g.Logger).Log("module", "net", "msg", er.Error()) 130 | // continue 131 | // } 132 | // for _, v2 := range data2.Nodes { 133 | // nodes[v2.Name] = struct{}{} 134 | // } 135 | //} 136 | 137 | // -------simple----- 138 | nodes := map[string]struct{}{} 139 | TG.Range(func(key, value any) bool { 140 | for _, v := range value.(*TopologyGraph).Nodes { 141 | nodes[v.Name] = struct{}{} 142 | } 143 | return true 144 | }) 145 | var res []string 146 | for k := range nodes { 147 | res = append(res, k) 148 | } 149 | sort.Strings(res) 150 | return res 151 | } 152 | 153 | func (h *MasterDiscover) UploadTsRecord(target string, data map[string]map[string]int64) { 154 | //var metrics []*pb.Metric 155 | ifDetail, er := GetIfDetails(target, nil) 156 | if er != nil { 157 | logrus.Error(er) 158 | return 159 | } 160 | var value float64 161 | t := time.Now().Unix() 162 | 163 | for metric, v := range data { 164 | for port, val := range v { 165 | if !math.IsInf(float64(val), 0) && !math.IsNaN(float64(val)) { 166 | value, _ = decimal.NewFromFloat(float64(val)).Round(2).Float64() 167 | } else { 168 | //fmt.Printf("invalid value %v for [%s %s %s]\n", val, node, valueType, direction) 169 | value = 0 170 | } 171 | if v2, ok := ifDetail[port]; ok { 172 | LocalMetrics <- &Metric{ 173 | Metric: metric, 174 | Type: "counter", 175 | Tags: map[string]string{ 176 | "host": target, 177 | "if": v2.Index, 178 | "port": v2.Port, 179 | "name": v2.Name, 180 | }, 181 | Timestamp: t, 182 | Value: value, 183 | } 184 | } 185 | } 186 | } 187 | } 188 | 189 | func consumerMetrics() { 190 | for m := range LocalMetrics { 191 | logrus.Info(m) 192 | } 193 | } 194 | 195 | //func updateLinks(links []*Link, graphId string) { 196 | // for _, v := range links { 197 | // t := &ConfigGraphLink{ 198 | // LinkNode: &LinkNode{ 199 | // Name: v.NodeFromName, 200 | // DeviceInterface: v.NodeFrom, 201 | // }, 202 | // To: v.NodeToName, 203 | // GraphId: graphId, 204 | // } 205 | // upsertConfigGraph(t) 206 | // } 207 | //} 208 | 209 | // 210 | //func upsertConfigGraph(data *ConfigGraphLink) { 211 | // data.LinkId = MD5(strings.Join([]string{data.Name, data.DeviceInterface, data.To}, "-")) 212 | // //data.Action = 3 213 | // //count, er := data.Count(db.ConfigGraphLinkCol, bson.M{"link_id": data.LinkId, "graph_id": data.GraphId}) 214 | // //if er != nil { 215 | // // _ = level.Error(g.Logger).Log("module", "net", "msg", er.Error()) 216 | // // return 217 | // //} 218 | // //if count == 0 { 219 | // // _, er = data.Add(db.ConfigGraphLinkCol, data) 220 | // // if er != nil { 221 | // // _ = level.Error(g.Logger).Log("module", "net", "msg", er.Error()) 222 | // // } 223 | // // return 224 | // //} 225 | // return 226 | //} 227 | -------------------------------------------------------------------------------- /netTopology/discover/models.go: -------------------------------------------------------------------------------- 1 | // Package models /* 2 | package discover 3 | 4 | import ( 5 | "go.mongodb.org/mongo-driver/bson/primitive" 6 | "time" 7 | ) 8 | 9 | type NodeSnmp struct { 10 | Ip string `json:"ip" bson:"ip"` 11 | Community string `json:"community" bson:"community"` 12 | Version string `json:"version" bson:"version"` 13 | } 14 | 15 | type GraphSnmp struct { 16 | BaseModel `bson:",inline"` 17 | Id primitive.ObjectID `json:"id" bson:"_id,omitempty"` 18 | Name string `json:"name" bson:"name"` 19 | Gateway string `json:"gateway" bson:"gateway"` 20 | Nodes []*NodeSnmp `json:"nodes" bson:"nodes"` 21 | Enable bool `json:"enable" bson:"enable"` 22 | OneAgent string `json:"one_agent" bson:"one_agent"` 23 | } 24 | 25 | type LinkNode struct { 26 | Name string `json:"name" bson:"name"` 27 | DeviceInterface string `json:"device_interface" bson:"device_interface"` 28 | // HighSpeed 单位 Mbps 29 | HighSpeed int `json:"high_speed" bson:"high_speed"` 30 | } 31 | 32 | type BaseModel struct { 33 | CreateTime time.Time `json:"create_time" bson:"create_time"` 34 | UpdateTime time.Time `json:"update_time" bson:"update_time"` 35 | Creator string `json:"creator" bson:"creator"` 36 | } 37 | 38 | type ConfigGraphLink struct { 39 | *BaseModel `bson:",inline"` 40 | *LinkNode `bson:",inline"` 41 | Id primitive.ObjectID `json:"-" bson:"_id,omitempty"` 42 | GraphId string `json:"-" bson:"graph_id"` 43 | // Name 自动生成,根据from,to的名称和type构成 44 | LinkId string `json:"link_id" bson:"link_id"` 45 | To string `json:"to" bson:"to"` 46 | Comment string `json:"comment" bson:"comment"` 47 | // Action 表示改链接的状态 48 | // 0: 人工删除, 1:人工确认或者添加 3:自动发现 49 | Action uint8 `json:"action" bson:"action"` 50 | } 51 | 52 | //type ConfigGraphNode struct { 53 | // BaseModel 54 | // Id primitive.ObjectID `json:"id" bson:"_id,omitempty"` 55 | // Name string `json:"name" bson:"name"` 56 | // DeviceInterfaces []*LinkNode `json:"device_interfaces" bson:"device_interfaces"` 57 | //} 58 | 59 | type Metric struct { 60 | Metric string `protobuf:"bytes,1,opt,name=metric,proto3" json:"metric,omitempty"` 61 | Value float64 `protobuf:"fixed64,2,opt,name=value,proto3" json:"value,omitempty"` 62 | Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` 63 | Tags map[string]string `protobuf:"bytes,4,rep,name=tags,proto3" json:"tags,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` 64 | Ns string `protobuf:"bytes,5,opt,name=ns,proto3" json:"ns,omitempty"` 65 | Type string `protobuf:"bytes,6,opt,name=type,proto3" json:"type,omitempty"` 66 | } 67 | -------------------------------------------------------------------------------- /netTopology/discover/var.go: -------------------------------------------------------------------------------- 1 | package discover 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | "github.com/patrickmn/go-cache" 8 | 9 | . "netTopology/internal" 10 | ) 11 | 12 | //type Node struct { 13 | // // Name must be unique 14 | // Name string `json:"name"` 15 | // // Alias 16 | // Alias string `json:"alias"` 17 | // // Type switch/router/server/internet/firewall/unknown 18 | // Type string `json:"type"` 19 | // // X location x 20 | // X int `json:"x"` 21 | // // Y location y 22 | // Y int `json:"y"` 23 | // // Ips 本机IP 24 | // Ips []string `json:"ips"` 25 | // // Locals 直连ip 26 | // Locals []string `json:"locals"` 27 | // // discoverIps 间接连接ip 28 | // discoverIps []string 29 | // Options interface{} `json:"options"` 30 | // Status string `json:"status"` 31 | //} 32 | 33 | // name2: Router_symbol_(96) 34 | // #bandwidth: 10 # 可以覆盖默认的link里的设置 35 | // #width: 5 36 | // #copy: true # 如果设置,即使拓扑里没这个元素,也会画出来 37 | // hostname: Router # 设置监控项采集的节点 38 | // itemin: net.if.in["eth0"] # 设置监控项的key 39 | // itemout: net.if.out["eth0"] # 设置监控项的key 40 | 41 | type Link struct { 42 | NodeFrom string `json:"node_from"` 43 | NodeTo string `json:"node_to"` 44 | NodeFromName string `json:"node_from_name"` 45 | NodeToName string `json:"node_to_name"` 46 | Value float64 `json:"value"` 47 | Unit string `json:"unit"` 48 | Level uint8 `json:"level"` 49 | IfIndex string `json:"if_index"` 50 | TrafficLoad float64 `json:"traffic_load"` 51 | // Port 物理接口 52 | Port string `json:"port"` 53 | Options interface{} `json:"options"` 54 | } 55 | 56 | type TopologyGraph struct { 57 | Name string `json:"name"` 58 | Nodes []*Node `json:"nodes"` 59 | Links []*Link `json:"links"` 60 | Location bool `json:"location"` 61 | GraphId string `json:"graph_id"` 62 | } 63 | 64 | type Arp struct { 65 | Target string 66 | // SelfIps 自身IP 67 | SelfIps []string 68 | // Subnets 连接的子网 69 | Subnets []string 70 | // LocalIps 子网ip 71 | LocalIps []string 72 | // PeerIps 间接ip列表,即平行连接ip列表,如交换机的nextHop 73 | PeerIps []string 74 | } 75 | 76 | const () 77 | 78 | var ( 79 | topology3Lock = sync.RWMutex{} 80 | 81 | IpScanWaiting = make(chan string, 10000) 82 | // IpScannedRecord 记录Ip是否扫描 83 | IpScannedRecord = sync.Map{} 84 | // IpEntOfDevice 设备的包含的IP列表 85 | IpEntOfDevice = sync.Map{} 86 | Topology3SyncWait = sync.WaitGroup{} 87 | // NetDevices 存放发现的 88 | NetDevices = make(chan string, 10000) 89 | DiscoverIpsCache = cache.New(time.Minute*5, time.Minute) 90 | 91 | //TargetStatus = cache.New(time.Hour, time.Minute*30) 92 | //CommunityMap = cache.New(time.Hour*24, time.Hour) 93 | 94 | MacIps = sync.Map{} 95 | L3Nodes = sync.Map{} 96 | L3Links = sync.Map{} 97 | Routes = sync.Map{} 98 | L2Switches = sync.Map{} 99 | IpMacAddress = sync.Map{} 100 | L2IpScanned = sync.Map{} 101 | 102 | SubnetsVar = &Subnets{M: map[string]*Subnet{}} 103 | // NodeLinkFinished 是否完成NodeLink 104 | NodeLinkFinished = sync.Map{} 105 | ScanArpFinished = sync.Map{} 106 | SubnetsScanned = sync.Map{} 107 | TotalIps = sync.Map{} 108 | LocalMetrics = make(chan *Metric, 1000) 109 | // TG map[string]*TopologyGraph{} 110 | TG = sync.Map{} 111 | ) 112 | 113 | func Init() { 114 | IpScanWaiting = make(chan string, 10000) 115 | IpScannedRecord = sync.Map{} 116 | IpEntOfDevice = sync.Map{} 117 | Topology3SyncWait = sync.WaitGroup{} 118 | NetDevices = make(chan string, 10000) 119 | DiscoverIpsCache = cache.New(time.Minute*5, time.Minute) 120 | MacIps = sync.Map{} 121 | L3Nodes = sync.Map{} 122 | L3Links = sync.Map{} 123 | Routes = sync.Map{} 124 | IpMacAddress = sync.Map{} 125 | NodeLinkFinished = sync.Map{} 126 | ScanArpFinished = sync.Map{} 127 | SubnetsScanned = sync.Map{} 128 | TotalIps = sync.Map{} 129 | 130 | ResultCache = cache.New(time.Hour, time.Minute*15) 131 | TargetStatus = cache.New(time.Hour, time.Minute*30) 132 | //CommunityMap = cache.New(time.Hour*24, time.Hour) 133 | //IpIdent = sync.Map{} 134 | IpNodeTypeMaps = cache.New(time.Hour*24*7, time.Hour) 135 | } 136 | -------------------------------------------------------------------------------- /netTopology/go.mod: -------------------------------------------------------------------------------- 1 | module netTopology 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/dustin/go-humanize v1.0.1 7 | github.com/gin-gonic/gin v1.9.1 8 | github.com/go-kit/kit v0.12.0 9 | github.com/go-kit/log v0.2.1 10 | github.com/go-ping/ping v1.1.0 11 | github.com/gomodule/redigo v1.8.9 12 | github.com/gosnmp/gosnmp v1.35.0 13 | github.com/jackpal/gateway v1.0.10 14 | github.com/mitchellh/hashstructure/v2 v2.0.2 15 | github.com/patrickmn/go-cache v2.1.0+incompatible 16 | github.com/pkg/errors v0.9.1 17 | github.com/shopspring/decimal v1.3.1 18 | github.com/sirupsen/logrus v1.8.1 19 | go.mongodb.org/mongo-driver v1.12.1 20 | golang.org/x/sync v0.3.0 21 | ) 22 | 23 | require ( 24 | github.com/bytedance/sonic v1.9.1 // indirect 25 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 26 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect 27 | github.com/gin-contrib/sse v0.1.0 // indirect 28 | github.com/go-logfmt/logfmt v0.5.1 // indirect 29 | github.com/go-playground/locales v0.14.1 // indirect 30 | github.com/go-playground/universal-translator v0.18.1 // indirect 31 | github.com/go-playground/validator/v10 v10.14.0 // indirect 32 | github.com/goccy/go-json v0.10.2 // indirect 33 | github.com/google/uuid v1.2.0 // indirect 34 | github.com/json-iterator/go v1.1.12 // indirect 35 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect 36 | github.com/leodido/go-urn v1.2.4 // indirect 37 | github.com/mattn/go-isatty v0.0.19 // indirect 38 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 39 | github.com/modern-go/reflect2 v1.0.2 // indirect 40 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect 41 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 42 | github.com/ugorji/go/codec v1.2.11 // indirect 43 | golang.org/x/arch v0.3.0 // indirect 44 | golang.org/x/crypto v0.9.0 // indirect 45 | golang.org/x/net v0.10.0 // indirect 46 | golang.org/x/sys v0.8.0 // indirect 47 | golang.org/x/text v0.9.0 // indirect 48 | google.golang.org/protobuf v1.30.0 // indirect 49 | gopkg.in/yaml.v3 v3.0.1 // indirect 50 | ) 51 | -------------------------------------------------------------------------------- /netTopology/go.sum: -------------------------------------------------------------------------------- 1 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= 2 | github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= 3 | github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= 4 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= 5 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= 6 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= 7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 11 | github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 12 | github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= 13 | github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= 14 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 15 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 16 | github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= 17 | github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= 18 | github.com/go-kit/kit v0.12.0 h1:e4o3o3IsBfAKQh5Qbbiqyfu97Ku7jrO/JbohvztANh4= 19 | github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs= 20 | github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= 21 | github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= 22 | github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= 23 | github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= 24 | github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw= 25 | github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk= 26 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 27 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 28 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 29 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 30 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 31 | github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= 32 | github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= 33 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 34 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 35 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 36 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 37 | github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws= 38 | github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE= 39 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 40 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 41 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 42 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 43 | github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= 44 | github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 45 | github.com/gosnmp/gosnmp v1.35.0 h1:EuWWNPxTCdAUx2/NbQcSa3WdNxjzpy4Phv57b4MWpJM= 46 | github.com/gosnmp/gosnmp v1.35.0/go.mod h1:2AvKZ3n9aEl5TJEo/fFmf/FGO4Nj4cVeEc5yuk88CYc= 47 | github.com/jackpal/gateway v1.0.10 h1:7g3fDo4Cd3RnTu6PzAfw6poO4Y81uNxrxFQFsBFSzJM= 48 | github.com/jackpal/gateway v1.0.10/go.mod h1:+uPBgIllrbkwYCAoDkGSZbjvpre/bGYAFCYIcrH+LHs= 49 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 50 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 51 | github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= 52 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 53 | github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= 54 | github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= 55 | github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= 56 | github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= 57 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= 58 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 59 | github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= 60 | github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= 61 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 62 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 63 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 64 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 65 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 66 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= 67 | github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= 68 | github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= 69 | github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= 70 | github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= 71 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 72 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 73 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 74 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 75 | github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= 76 | github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= 77 | github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= 78 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 79 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 80 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 81 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 82 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 83 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 84 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 85 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 86 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 87 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 88 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 89 | github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= 90 | github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 91 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 92 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 93 | github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= 94 | github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 95 | github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= 96 | github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= 97 | github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= 98 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= 99 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 100 | go.mongodb.org/mongo-driver v1.12.1 h1:nLkghSU8fQNaK7oUmDhQFsnrtcoNy7Z6LVFKsEecqgE= 101 | go.mongodb.org/mongo-driver v1.12.1/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ= 102 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 103 | golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= 104 | golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 105 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 106 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 107 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 108 | golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= 109 | golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= 110 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 111 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 112 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 113 | golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= 114 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 115 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 116 | golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= 117 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 118 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 119 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 120 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 121 | golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= 122 | golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 123 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 124 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 125 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 126 | golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 127 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 128 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 129 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 130 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 131 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 132 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 133 | golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= 134 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 135 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 136 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 137 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 138 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 139 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 140 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 141 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 142 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 143 | golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= 144 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 145 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 146 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 147 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 148 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 149 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 150 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 151 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 152 | google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= 153 | google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 154 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 155 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 156 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 157 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 158 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 159 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 160 | -------------------------------------------------------------------------------- /netTopology/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/ops-tools/c8912d0e51de78cfed487f57553ccf3f6b4d394e/netTopology/img.png -------------------------------------------------------------------------------- /netTopology/internal/common.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "fmt" 5 | "github.com/go-ping/ping" 6 | gsnmp "github.com/gosnmp/gosnmp" 7 | "math/big" 8 | "net" 9 | "strconv" 10 | "strings" 11 | "sync" 12 | "sync/atomic" 13 | "time" 14 | ) 15 | 16 | var ( 17 | // CidrSpan 默认最大网络号 18 | CidrSpan = 20 19 | // SpecialCIDRs 对于超过4094位的需要单独指定 20 | // 如 {"1.1.1.1/19":""} 21 | SpecialCIDRs = sync.Map{} 22 | ) 23 | 24 | func InetItoa(ip int64) string { 25 | return fmt.Sprintf("%d.%d.%d.%d", 26 | byte(ip>>24), byte(ip>>16), byte(ip>>8), byte(ip)) 27 | } 28 | 29 | func InetAtoi(ip string) int64 { 30 | ret := big.NewInt(0) 31 | ret.SetBytes(net.ParseIP(strings.TrimSpace(ip)).To4()) 32 | return ret.Int64() 33 | } 34 | 35 | func PingHost(hostName string) bool { 36 | p, err := ping.NewPinger(hostName) 37 | if err != nil { 38 | return false 39 | } 40 | p.Timeout = time.Second 41 | p.SetPrivileged(true) 42 | p.Count = 3 43 | err = p.Run() // Blocks until finished. 44 | if err != nil { 45 | return false 46 | } 47 | return p.Statistics().PacketsRecv > 0 48 | } 49 | 50 | // PortScan 扫描ip端口 51 | // @params ignorePort bool 是否忽略端口返回,此种情况只是为了在icmp协议禁用之后,判断主机是否在线 52 | // @return available bool 是否在线 53 | // @return ports []int 在线端口列表 54 | func PortScan(ipStr string, ignorePort bool, scanPorts []int) (available bool, ports []int) { 55 | var wg sync.WaitGroup 56 | var ch = make(chan struct{}, 50) 57 | var lock sync.Mutex 58 | var chClosed atomic.Bool 59 | chClosed.Store(false) 60 | for j := 1; j <= 65535; j++ { 61 | if len(scanPorts) > 0 && !InSlice(j, scanPorts) { 62 | continue 63 | } 64 | wg.Add(1) 65 | if chClosed.Load() { 66 | wg.Done() 67 | break 68 | } 69 | ch <- struct{}{} 70 | go func(i int) { 71 | defer wg.Done() 72 | <-ch 73 | if chClosed.Load() { 74 | return 75 | } 76 | var address = fmt.Sprintf("%s:%d", ipStr, i) 77 | conn, err := net.DialTimeout("tcp", address, time.Second*10) 78 | if err != nil { 79 | return 80 | } 81 | conn.Close() 82 | if ignorePort { 83 | chClosed.Store(true) 84 | } else { 85 | lock.Lock() 86 | ports = append(ports, i) 87 | lock.Unlock() 88 | } 89 | }(j) 90 | } 91 | wg.Wait() 92 | return 93 | } 94 | 95 | func InSlice(a int, list []int) bool { 96 | for _, b := range list { 97 | if b == a { 98 | return true 99 | } 100 | } 101 | return false 102 | } 103 | 104 | func StringInSlice(a string, list []string) bool { 105 | for _, b := range list { 106 | if b == a { 107 | return true 108 | } 109 | } 110 | return false 111 | } 112 | 113 | func StringConcat(str ...string) string { 114 | var builder strings.Builder 115 | for _, v := range str { 116 | builder.WriteString(v) 117 | } 118 | return builder.String() 119 | } 120 | 121 | func inc(ip net.IP) { 122 | for j := len(ip) - 1; j >= 0; j-- { 123 | ip[j]++ 124 | if ip[j] > 0 { 125 | break 126 | } 127 | } 128 | } 129 | 130 | func containsCIDR(cr1, cr2 string) bool { 131 | _, a, _ := net.ParseCIDR(cr1) 132 | _, b, _ := net.ParseCIDR(cr2) 133 | ones1, _ := a.Mask.Size() 134 | ones2, _ := b.Mask.Size() 135 | return ones1 <= ones2 && a.Contains(b.IP) 136 | } 137 | 138 | // acceptCidr 当获取一个网段时,网段太大,扫描负担很重,因此默认值采集最小255.255.16.0,即网络号至少20位是网络号 139 | // 如有特殊需求,需明确指定cidr范围 140 | // cidr: 192.168.1.1/24 141 | func validtCidr(cidr string) bool { 142 | //tmp := strings.Split(cidr, "/") 143 | //var sz int 144 | //// 1.1.1.1/255.255.255.0 145 | //if len(tmp) == 2 && len(tmp[1]) > 2 { 146 | // adr := net.ParseIP(tmp[1]).To4() 147 | // sz, _ = net.IPv4Mask(adr[0], adr[1], adr[2], adr[3]).Size() 148 | // if sz >= CidrSpan { 149 | // return true 150 | // } else { 151 | // cidr = fmt.Sprintf("%s/%d", tmp[0], sz) 152 | // } 153 | //} 154 | _, ipNet, _ := net.ParseCIDR(cidr) 155 | if m, _ := ipNet.Mask.Size(); m >= CidrSpan { 156 | return true 157 | } 158 | var valid bool 159 | SpecialCIDRs.Range(func(key, value any) bool { 160 | if containsCIDR(key.(string), cidr) { 161 | valid = true 162 | return false 163 | } 164 | return true 165 | }) 166 | return valid 167 | } 168 | 169 | // CidrHosts Convert Cidr Address To Hosts 170 | // newAddr : "192.168.1.0/24" or "192.168.1.0/255.255.255.0" 171 | // 防止返回过多ip,此处限制最多返回一个B段 172 | func CidrHosts(netAddr string) ([]string, error) { 173 | tmp := strings.Split(netAddr, "/") 174 | if len(tmp) == 2 && len(tmp[1]) > 2 { 175 | adr := net.ParseIP(tmp[1]).To4() 176 | sz, _ := net.IPv4Mask(adr[0], adr[1], adr[2], adr[3]).Size() 177 | netAddr = fmt.Sprintf("%s/%d", tmp[0], sz) 178 | } 179 | if !validtCidr(netAddr) { 180 | return nil, fmt.Errorf("ip range is too large for %s, skipped", netAddr) 181 | } 182 | ipAddr, ipv4Net, err := net.ParseCIDR(netAddr) 183 | if err != nil { 184 | return nil, err 185 | } 186 | var ips []string 187 | for ip := ipAddr.Mask(ipv4Net.Mask); ipv4Net.Contains(ip); inc(ip) { 188 | ips = append(ips, ip.String()) 189 | if len(ips) > 65535 { 190 | break 191 | } 192 | } 193 | //mask := binary.BigEndian.Uint32(ipv4Net.Mask) 194 | //start := binary.BigEndian.Uint32(ipv4Net.IP) 195 | //finish := (start & mask) | (mask ^ 0xffffffff) 196 | //var hosts []string 197 | //tmp1 := 0 198 | //for i := start + 1; i <= finish-1; i++ { 199 | // if tmp1 > 100 { 200 | // fmt.Println("break", finish, mask, start, mask, start&mask, mask^0xffffffff) 201 | // break 202 | // } 203 | // ip := make(net.IP, 4) 204 | // binary.BigEndian.PutUint32(ip, i) 205 | // hosts = append(hosts, ip.String()) 206 | // tmp1++ 207 | //} 208 | if len(ips) > 2 { 209 | return ips[1 : len(ips)-1], nil 210 | } 211 | return []string{}, nil 212 | } 213 | 214 | // MacAddressConvert 215 | // from: 11.21.31.41.51.61 --> b:15:1f:29:33:3d 216 | func MacAddressConvert(from string) string { 217 | //.1.3.6.1.2.1.4.22.1.2 218 | v := strings.Split(from, ".") 219 | var s []string 220 | for _, v1 := range v { 221 | if v1 == "" { 222 | continue 223 | } 224 | d, er := strconv.Atoi(v1) 225 | if er != nil { 226 | return from 227 | } 228 | s = append(s, fmt.Sprintf("%x", d)) 229 | } 230 | return strings.Join(s, ":") 231 | } 232 | 233 | func ValidInnerIp(ip string) bool { 234 | // 235 | validInnerIps := []string{ 236 | // 10.0.0.0/8 237 | "10.0.0.0-10.255.255.255", 238 | // 172.16.0.0/12 239 | "172.16.0.0-172.31.255.255", 240 | // 192.168.0.0/16 241 | "192.168.0.0-192.168.255.255", 242 | // 198.18.0.0/15 用于测试两个独立子网的网间通信 243 | "198.18.0.0-198.19.255.255", 244 | // 198.51.100.0/24 245 | "198.51.100.0-198.51.100.255", 246 | //203.0.113.0/24 247 | "203.0.113.0-203.0.113.255", 248 | } 249 | for _, v := range validInnerIps { 250 | ipSlice := strings.Split(v, `-`) 251 | if len(ipSlice) < 0 { 252 | continue 253 | } 254 | if InetAtoi(ip) >= InetAtoi(ipSlice[0]) && InetAtoi(ip) <= InetAtoi(ipSlice[1]) { 255 | return true 256 | } 257 | } 258 | var valid bool 259 | SpecialCIDRs.Range(func(key, value any) bool { 260 | t := net.ParseIP(ip) 261 | if t != nil { 262 | _, a, _ := net.ParseCIDR(key.(string)) 263 | if a.Contains(t) { 264 | valid = true 265 | return false 266 | } 267 | } 268 | return true 269 | }) 270 | return valid 271 | } 272 | 273 | func HardwareAddr(addr interface{}) string { 274 | switch address := addr.(type) { 275 | case string: 276 | return address 277 | case []byte: 278 | return net.HardwareAddr(address).String() 279 | } 280 | return "" 281 | } 282 | 283 | func ToHardwareAddr(val interface{}) string { 284 | switch value := val.(type) { 285 | case string: 286 | tmp := strings.Split(value, " ") 287 | if len(tmp) == 6 { 288 | n := []string{} 289 | for _, v := range tmp { 290 | v1, er := strconv.ParseUint(v, 10, 64) 291 | if er != nil { 292 | return val.(string) 293 | } 294 | n = append(n, fmt.Sprintf("%x", v1)) 295 | } 296 | return strings.Join(n, ":") 297 | } else { 298 | return val.(string) 299 | } 300 | case []byte: 301 | n := []string{} 302 | for _, v := range value { 303 | n = append(n, fmt.Sprintf("%x", v)) 304 | } 305 | return strings.Join(n, ":") 306 | } 307 | return "" 308 | 309 | } 310 | 311 | func RmDupElement(arg []string) []string { 312 | tmp := map[string]int{} 313 | for _, el := range arg { 314 | if _, ok := tmp[el]; !ok { 315 | tmp[el] = 1 316 | } 317 | } 318 | var result []string 319 | for k := range tmp { 320 | result = append(result, k) 321 | } 322 | return result 323 | } 324 | 325 | // Final 326 | // {"a":"b", "b":"c", "c":"d"}, final("a") wil get "d" 327 | func Final(key string, data map[string]string) string { 328 | if v, ok := data[key]; ok { 329 | if key == v { 330 | return key 331 | } 332 | return Final(v, data) 333 | } else { 334 | return key 335 | } 336 | } 337 | 338 | //func macAddress(from string) string { 339 | // var tmp []string 340 | // for _, v1 := range strings.Split(from, " ") { 341 | // t, er := strconv.ParseUint(v1, 10, 8) 342 | // if er != nil { 343 | // break 344 | // } 345 | // tmp = append(tmp, fmt.Sprintf("%x", t)) 346 | // } 347 | // if len(tmp) < 6 { 348 | // return from 349 | // } 350 | // tmp = tmp[len(tmp)-6:] 351 | // return strings.Join(tmp, ":") 352 | //} 353 | 354 | //func SnmpWalk(snmp *gsnmp.GoSNMP, action string, oids ...string) (res []*SnmpResp, err error) { 355 | // if len(oids) == 0 { 356 | // return res, fmt.Errorf("oid is none") 357 | // } 358 | // var result []gsnmp.SnmpPDU 359 | // if action == "get" { 360 | // r, er := snmp.Get(oids) 361 | // if er != nil { 362 | // err = er 363 | // return 364 | // } 365 | // result = r.Variables 366 | // } else { 367 | // result, err = snmp.BulkWalkAll(oids[0]) 368 | // if err != nil { 369 | // return 370 | // } 371 | // } 372 | // for _, v := range result { 373 | // tmp := SnmpResp{} 374 | // tmp.Oid = v.Name 375 | // switch v.Type { 376 | // case gsnmp.IPAddress: 377 | // tmp.DataType = "string" 378 | // tmp.DataValue = fmt.Sprintf("%v", v.Value) 379 | // case gsnmp.OctetString: 380 | // tmp.DataType = "string" 381 | // value := "" 382 | // // HEX-STRING 383 | // if strings.Contains(strconv.Quote(string(v.Value.([]byte))), "\\x") { 384 | // for i := 0; i < len(v.Value.([]byte)); i++ { 385 | // value += fmt.Sprintf("%v", v.Value.([]byte)[i]) 386 | // if i != (len(v.Value.([]byte)) - 1) { 387 | // value += " " 388 | // } 389 | // } 390 | // tmp.DataValue = value 391 | // } else { 392 | // value = string(v.Value.([]byte)) 393 | // tmp.DataValue = value 394 | // } 395 | // default: 396 | // tmp.DataType = "integer" 397 | // tmp.DataValue = gsnmp.ToBigInt(v.Value) 398 | // //tmp.DataValue = fmt.Sprintf("%d", v.Value) 399 | // } 400 | // tmp.Raw = v.Value 401 | // res = append(res, &tmp) 402 | // } 403 | // return res, nil 404 | // 405 | //} 406 | 407 | func SnmpWalk(snmp *gsnmp.GoSNMP, action string, oids ...string) (res []*SnmpResp, err error) { 408 | if len(oids) == 0 { 409 | return res, fmt.Errorf("oid is none") 410 | } 411 | var result []gsnmp.SnmpPDU 412 | if action == "get" { 413 | r, er := snmp.Get(oids) 414 | if er != nil { 415 | err = er 416 | return 417 | } 418 | result = r.Variables 419 | } else if action == "first" { 420 | err = snmp.BulkWalk(oids[0], func(dataUnit gsnmp.SnmpPDU) error { 421 | if dataUnit.Value != nil { 422 | result = append(result, dataUnit) 423 | return fmt.Errorf("%s", "skip") 424 | } 425 | return nil 426 | }) 427 | if err != nil { 428 | return 429 | } 430 | } else if action == "firstNotNone" { 431 | err = snmp.BulkWalk(oids[0], func(dataUnit gsnmp.SnmpPDU) error { 432 | if dataUnit.Value != nil && dataUnit.Type == gsnmp.OctetString && len(dataUnit.Value.([]byte)) != 0 { 433 | result = append(result, dataUnit) 434 | return fmt.Errorf("%s", "skip") 435 | } 436 | return nil 437 | }) 438 | if err != nil { 439 | return 440 | } 441 | } else { 442 | result, err = snmp.BulkWalkAll(oids[0]) 443 | if err != nil { 444 | return 445 | } 446 | } 447 | for _, v := range result { 448 | tmp := parseResult(v) 449 | tmp.Raw = v.Value 450 | res = append(res, &tmp) 451 | } 452 | return res, nil 453 | } 454 | 455 | func parseResult(v gsnmp.SnmpPDU) SnmpResp { 456 | tmp := SnmpResp{} 457 | tmp.Oid = v.Name 458 | switch v.Type { 459 | case gsnmp.IPAddress, gsnmp.ObjectIdentifier: 460 | tmp.DataType = "string" 461 | tmp.DataValue = fmt.Sprintf("%v", v.Value) 462 | case gsnmp.OctetString: 463 | tmp.DataType = "string" 464 | value := "" 465 | // HEX-STRING 466 | if strings.Contains(strconv.Quote(string(v.Value.([]byte))), "\\x") { 467 | for i := 0; i < len(v.Value.([]byte)); i++ { 468 | value += fmt.Sprintf("%v", v.Value.([]byte)[i]) 469 | if i != (len(v.Value.([]byte)) - 1) { 470 | value += " " 471 | } 472 | } 473 | tmp.DataValue = value 474 | } else { 475 | value = string(v.Value.([]byte)) 476 | tmp.DataValue = value 477 | } 478 | default: 479 | tmp.DataType = "integer" 480 | tmp.DataValue = gsnmp.ToBigInt(v.Value) 481 | //tmp.DataValue = fmt.Sprintf("%d", v.Value) 482 | } 483 | return tmp 484 | } 485 | -------------------------------------------------------------------------------- /netTopology/internal/node_type.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | type NodeType int 4 | 5 | const ( 6 | NTServer NodeType = iota 7 | NTSwitch 8 | NTRouter 9 | NTInternet 10 | NTOffline 11 | NTOther 12 | ) 13 | 14 | func (nt NodeType) String() string { 15 | switch nt { 16 | case NTServer: 17 | return "server" 18 | case NTSwitch: 19 | return "switch" 20 | case NTRouter: 21 | return "router" 22 | case NTInternet: 23 | return "internet" 24 | case NTOffline: 25 | return "offline" 26 | default: 27 | return "other" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /netTopology/internal/oids.go: -------------------------------------------------------------------------------- 1 | // Package netTopology /* 2 | package internal 3 | 4 | // Sys 设备信息, Get 信息 5 | type Sys struct { 6 | // SysName 主机名 7 | SysName string 8 | // SysDesc 产商信息 9 | SysDesc string 10 | // SysUpTimeInstance 监控时间 11 | SysUpTimeInstance string 12 | // SysLocation 设备位置 13 | SysLocation string 14 | } 15 | 16 | // Dot1dBridge mac地址表,即mac与端口的映射关系 17 | type Dot1dBridge struct { 18 | // Dot1dTpFdbAddress 桥接转发表, mac地址 19 | // 在这里,需要用到Community String Indexing,思科设备的转发表对每个vlan是不一样的。这时候共同体名为community@vlank号, 20 | // 如:public@1,public@2,public@3等,默认取public@1地的转发表。此时需在对每个vlan的转发表进行查找,直到找到ip对应的mac地址 21 | Dot1dTpFdbAddress string 22 | // Dot1dTpFdbPort 转发端口, 对应物理端口 Port Number 23 | Dot1dTpFdbPort string 24 | } 25 | 26 | type Dot1dStp struct { 27 | // Dot1dStpPortTable 28 | Dot1dStpPortTable string 29 | // Dot1dStpPortDesignatedBridge 对应端口所连接的另一台二层设备的BridgeID, 与dot1dBaseBridgeAddress对应 30 | Dot1dStpPortDesignatedBridge string 31 | // Dot1dStpPortDesignatedPort 该二层设备的某个端口连接了另一台二层设备的哪个桥接端口 32 | Dot1dStpPortDesignatedPort string 33 | // Dot1dStpRootCost 从该网桥到根的路径成本 34 | // 两台交换机之间的连接信息是由其值较大(离root较远)的那一台交换机产生的,另一台则以自身信息作为默认 35 | // 值越大,在生成树种的层级越低,两台交换机的连接关系由层级较低的交换机确定 36 | Dot1dStpRootCost string 37 | // Dot1dStpPort 二层代理设备各个端口的桥接端口号/包含生成树协议管理信息的端口的端口号 38 | Dot1dStpPort string 39 | // Dot1dStpPortState 表明端口的工作状态 40 | //1: disabled; 2:blocking(冗余链路); 3:listening; 4:learning; 5:forwarding; 6:broken 41 | // spanning-tree技术把冗余角色的备用线路设置成forwarding(5)以外的其他状态 42 | Dot1dStpPortState string 43 | } 44 | 45 | type Dot1dBase struct { 46 | // Dot1dBasePort 物理端口号 47 | Dot1dBasePort string 48 | // Dot1dBasePortIfIndex 端口对应的接口索引 49 | Dot1dBasePortIfIndex string 50 | // Dot1dBaseBridgeAddress 代表设备自身MAC地址 .1.3.6.1.2.1.17.1.1 51 | Dot1dBaseBridgeAddress string 52 | } 53 | 54 | // IpNetToMedia 该表是一张IP地址转换表,主要是将IP地址映射为物理地址;该表的索引是ipNetToMediaIfIndex和ipNetToMediaNetAddress 55 | // 通过其可以发现与该设备相连的其他设备的ip地址和物理地址的映射以及映射方式,从而发现一些设备的连接情况 56 | type IpNetToMedia struct { 57 | // ipNetToMediaIfIndex 表示此表项对应的有效接口的索引值, 该值所指定的接口与IF-MIB中的ifIndex值所指定接口相同 58 | IpNetToMediaIfIndex string 59 | // IpNetToMediaPhysAddress arp表. 表示依据媒介而定的物理地址, 即ip与mac地址的映射关系 60 | IpNetToMediaPhysAddress string 61 | // IpNetToMediaNetAddress arp表中 mac对应IP, 和本机在一个子网的ip。 表示这个依据媒介而定的物理地址对应的IP地址。 62 | IpNetToMediaNetAddress string 63 | // ipNetToMediaType other(1),invalid(2) dynamic(3):动态,即对端设备的ip;static(4):静态,代表本机的Ip 64 | IpNetToMediaType string 65 | } 66 | 67 | type Ip struct { 68 | //ipForwarding = "" 69 | // IpForwarding 是否具有路由功能, 1: 有路由功能 2: 无路由功能 70 | IpForwarding string 71 | // PrintMib 是否为打印机,如不为空,则为打印机 72 | PrintMib string 73 | } 74 | 75 | type IpRoute struct { 76 | // IpRouteDest 路由表(目标网络) 77 | IpRouteDest string 78 | // IpRouteIfIndex 路由表(出接口索引 唯一标识本地接口的索引值,通过该接口可以到达该路由的下一站) 79 | IpRouteIfIndex string 80 | // IpRouteNextHop 显示这条路由下一跳的IP地址. 当路由与广播媒介接口绑定时,该节点的值为接口上代理的IP地址 81 | IpRouteNextHop string 82 | // IpRouteType路由类型 83 | // 3(direct):直接路由,表明目标网络或者目标主机与该路由器直接相连 84 | // 4(indirect):间接路由,表明通往目的网络或者目的主机的的路径上还要经过其他路由器 85 | // 直连路由:直接连接到路由器端口的网段,该信息由路由器自动生成; 86 | // 非直连路由:不是直接连接到路由器端口的网段,此记录需要手动添加或使用动态路由生成。 87 | IpRouteType string 88 | IpRouteMask string 89 | } 90 | 91 | type If struct { 92 | // IfName interface 名称 93 | IfName string 94 | IfIndex string 95 | // 网卡描述 96 | IfDes string 97 | // IfHighSpeed 带宽限制 98 | IfHighSpeed string 99 | IfHCInOctets string 100 | IfHCOutOctets string 101 | IfInDiscards string 102 | IfOutDiscards string 103 | 104 | // IfOutUcastPkts 输出非广播包数 105 | IfOutUcastPkts string 106 | IfInUcastPkts string 107 | // ifOutNUcastPkts 广播包和多点发送包计数,速率可以识别广播风暴 108 | IfOutNUcastPkts string 109 | IfInNUcastPkts string 110 | IfOutErrors string 111 | IfInErrors string 112 | // IfAdminStatus 接口的配置状态; up(1),down(2),testing(3) 113 | IfAdminStatus string 114 | // IfOperStatus 接口的当前工作状态up(1),down(2),testing(3) 115 | IfOperStatus string 116 | } 117 | 118 | // IpAdEnt ipAddrTable 该表主要是用来保存IP地址信息,如IP地址、子网掩码等; 该表的索引是ipAdEntAddr。 119 | type IpAdEnt struct { 120 | // IpAdEntAddr 显示这个表项的地址信息所属的IP地址 121 | IpAdEntAddr string 122 | // IpAdEntIfIndex 唯一标识该表项所应用的接口的索引值 123 | IpAdEntIfIndex string 124 | // IpAdEntNetMask 显示该IP地址的子网掩码 125 | IpAdEntNetMask string 126 | } 127 | 128 | type EntPhys struct { 129 | // entPhysicalDescr 设备各模块描述 130 | EntPhysicalDescr string 131 | // EntPhysicalName 设备模块物理名称 132 | EntPhysicalName string 133 | // EntPhysicalSerialNum 设备各模块序列号 134 | EntPhysicalSerialNum string 135 | // EntPhysicalMfgName 设备各模块产商 136 | EntPhysicalMfgName string 137 | } 138 | 139 | type Neighbor struct { 140 | // Neighbor LLDP邻居信息 1.0.8802.1.1.2.1.4 141 | Neighbor string 142 | } 143 | 144 | type AtEntry struct { 145 | AtPhysAddress string 146 | } 147 | 148 | type OidLibrary struct { 149 | Sys 150 | Dot1dBridge 151 | Dot1dBase 152 | IpNetToMedia 153 | Ip 154 | IpRoute 155 | If 156 | IpAdEnt 157 | Dot1dStp 158 | EntPhys 159 | } 160 | 161 | var ( 162 | OLibrary = &OidLibrary{ 163 | Sys{ 164 | SysName: ".1.3.6.1.2.1.1.5.", 165 | SysDesc: ".1.3.6.1.2.1.1.1", 166 | SysUpTimeInstance: ".1.3.6.1.2.1.1.3", 167 | SysLocation: ".1.3.6.1.2.1.1.6", 168 | }, 169 | Dot1dBridge{ 170 | Dot1dTpFdbAddress: ".1.3.6.1.2.1.17.4.3.1.1", 171 | Dot1dTpFdbPort: ".1.3.6.1.2.1.17.4.3.1.2", 172 | }, 173 | Dot1dBase{ 174 | Dot1dBaseBridgeAddress: ".1.3.6.1.2.1.17.1.1.0", 175 | Dot1dBasePort: ".1.3.6.1.2.1.17.1.4.1.1", 176 | Dot1dBasePortIfIndex: ".1.3.6.1.2.1.17.1.4.1.2", 177 | }, 178 | IpNetToMedia{ 179 | IpNetToMediaIfIndex: ".1.3.6.1.2.1.4.22.1.1", 180 | IpNetToMediaPhysAddress: ".1.3.6.1.2.1.4.22.1.2", 181 | IpNetToMediaNetAddress: ".1.3.6.1.2.1.4.22.1.3", 182 | IpNetToMediaType: ".1.3.6.1.2.1.4.22.1.4", 183 | }, 184 | Ip{ 185 | IpForwarding: ".1.3.6.1.2.1.4.1", 186 | PrintMib: ".1.3.6.1.2.1.43", 187 | }, 188 | IpRoute{ 189 | IpRouteDest: ".1.3.6.1.2.1.4.21.1.1", 190 | IpRouteIfIndex: ".1.3.6.1.2.1.4.21.1.2", 191 | IpRouteNextHop: ".1.3.6.1.2.1.4.21.1.7", 192 | IpRouteType: ".1.3.6.1.2.1.4.21.1.8", 193 | IpRouteMask: ".1.3.6.1.2.1.4.21.1.11", 194 | }, 195 | If{ 196 | IfName: ".1.3.6.1.2.1.31.1.1.1.1", 197 | IfIndex: ".1.3.6.1.2.1.2.2.1.1", 198 | IfDes: ".1.3.6.1.2.1.2.2.1.2", 199 | IfAdminStatus: ".1.3.6.1.2.1.2.2.1.7", 200 | IfOperStatus: ".1.3.6.1.2.1.2.2.1.8", 201 | IfInUcastPkts: ".1.3.6.1.2.1.2.2.1.11", 202 | IfInNUcastPkts: ".1.3.6.1.2.1.2.2.1.12", 203 | IfInDiscards: ".1.3.6.1.2.1.2.2.1.13", 204 | IfInErrors: ".1.3.6.1.2.1.2.2.1.14", 205 | IfOutUcastPkts: ".1.3.6.1.2.1.2.2.1.17", 206 | IfOutNUcastPkts: ".1.3.6.1.2.1.2.2.1.18", 207 | IfOutDiscards: ".1.3.6.1.2.1.2.2.1.19", 208 | IfOutErrors: ".1.3.6.1.2.1.2.2.1.20", 209 | 210 | IfHighSpeed: ".1.3.6.1.2.1.31.1.1.1.15", 211 | IfHCInOctets: ".1.3.6.1.2.1.31.1.1.1.6", 212 | IfHCOutOctets: ".1.3.6.1.2.1.31.1.1.1.10", 213 | 214 | //ifAdminStatus 215 | // 216 | // 217 | // 218 | //用于配置接口的状态(可读写)up(1),down(2),testing(3)(见表2) 219 | // 220 | //ifOperStatus 221 | // 222 | // 223 | // 224 | //提供接口的当前工作状态up(1),down(2),testing(3) 225 | // 226 | //(见表2) 227 | }, 228 | IpAdEnt{ 229 | IpAdEntAddr: ".1.3.6.1.2.1.4.20.1.1", 230 | IpAdEntIfIndex: ".1.3.6.1.2.1.4.20.1.2", 231 | IpAdEntNetMask: ".1.3.6.1.2.1.4.20.1.3", 232 | }, 233 | Dot1dStp{ 234 | Dot1dStpRootCost: ".1.3.6.1.2.1.17.2.6", 235 | Dot1dStpPortTable: ".1.3.6.1.2.1.17.2.15", 236 | Dot1dStpPort: ".1.3.6.1.2.1.17.2.15.1.1", 237 | Dot1dStpPortState: ".1.3.6.1.2.1.17.2.15.1.3", 238 | Dot1dStpPortDesignatedBridge: ".1.3.6.1.2.1.17.2.15.1.8", 239 | Dot1dStpPortDesignatedPort: ".1.3.6.1.2.1.17.2.15.1.9", 240 | }, 241 | EntPhys{ 242 | EntPhysicalDescr: ".1.3.6.1.2.1.47.1.1.1.1.2", 243 | EntPhysicalName: ".1.3.6.1.2.1.47.1.1.1.1.7", 244 | EntPhysicalSerialNum: ".1.3.6.1.2.1.47.1.1.1.1.11", 245 | EntPhysicalMfgName: ".1.3.6.1.2.1.47.1.1.1.1.12", 246 | }, 247 | } 248 | ) 249 | -------------------------------------------------------------------------------- /netTopology/internal/types.go: -------------------------------------------------------------------------------- 1 | // Package internal /* 2 | package internal 3 | 4 | //import "serinus/pkg/server/models" 5 | 6 | type LinkValue struct { 7 | From string `json:"from"` 8 | To string `json:"to"` 9 | Key string `json:"key"` 10 | Value interface{} `json:"value"` 11 | Unit string `json:"unit"` 12 | Alias string `json:"alias"` 13 | Speed int `json:"speed"` 14 | Status interface{} `json:"status"` 15 | } 16 | 17 | //type LinkV1 struct { 18 | // // Name 自动生成,根据from,to的名称和type构成 19 | // LinkId string `json:"link_id"` 20 | // Node1 *models.LinkNode `json:"node1"` 21 | // Node2 *models.LinkNode `json:"node2"` 22 | // Values []*LinkValue `json:"values"` 23 | // Options interface{} `json:"options"` 24 | // Status string `json:"status"` 25 | //} 26 | 27 | type NodeStatus struct { 28 | Status string `json:"status"` 29 | Value string `json:"value"` 30 | Metric string `json:"metric"` 31 | Alias string `json:"alias"` 32 | } 33 | 34 | type Node struct { 35 | // Name must be unique 36 | Name string `json:"name"` 37 | // Alias 38 | Alias string `json:"alias"` 39 | // Type switch/router/server/internet/firewall/unknown 40 | Type string `json:"type"` 41 | // X location x 42 | X int `json:"x"` 43 | // Y location y 44 | Y int `json:"y"` 45 | // Ips 本机IP 46 | Ips []string `json:"ips"` 47 | // Locals 直连ip 48 | Locals []string `json:"locals"` 49 | // discoverIps 间接连接ip 50 | Options interface{} `json:"options"` 51 | Status string `json:"status"` 52 | IfIndexIps map[int64][]string `json:"if_index_ips"` 53 | DesIps map[string][]string `json:"des_ips"` 54 | } 55 | 56 | //type TopologyGraphV1 struct { 57 | // Name string `json:"name"` 58 | // Nodes []*Node `json:"nodes"` 59 | // Links []*LinkV1 `json:"links"` 60 | // Location bool `json:"location"` 61 | // Illustrate interface{} `json:"illustrate"` 62 | // StatusDetails map[string]map[string]map[string]*NodeStatus `json:"status_details"` 63 | //} 64 | -------------------------------------------------------------------------------- /netTopology/internal/utils.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | "fmt" 7 | gsnmp "github.com/gosnmp/gosnmp" 8 | "github.com/patrickmn/go-cache" 9 | "github.com/sirupsen/logrus" 10 | "io" 11 | "sort" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | const ( 17 | defaultCommunity = "public" 18 | defaultCommunityV2 = "publicv2" 19 | ) 20 | 21 | var ( 22 | TargetStatus = cache.New(time.Minute, time.Minute) 23 | CommunityMap = cache.New(time.Hour*24, time.Hour) 24 | SnmpVersionMap = cache.New(time.Hour*24, time.Hour) 25 | ResultCache = cache.New(time.Hour, time.Minute*15) 26 | IpNodeTypeMaps = cache.New(time.Hour*24*7, time.Hour) 27 | // IpIdent ip映射,当一个设备有多个ip时,统一为一个IP采集数据 28 | IpIdent = cache.New(time.Hour*24*7, time.Hour*24) 29 | ) 30 | 31 | type SnmpResp struct { 32 | Domain string `json:"domain"` 33 | Oid string `json:"oid"` 34 | Key string `json:"key"` 35 | DataType string `json:"data_type"` 36 | DataValue interface{} `json:"data_value"` 37 | Raw interface{} `json:"raw"` 38 | } 39 | 40 | func NewSnmp(target string) (*gsnmp.GoSNMP, error) { 41 | if v, ok := TargetStatus.Get(target); ok { 42 | if !v.(bool) { 43 | return nil, fmt.Errorf("connect error") 44 | } 45 | } 46 | if !ValidInnerIp(target) { 47 | return nil, fmt.Errorf("invalid intranet ip for %s", target) 48 | } 49 | c := defaultCommunityV2 50 | if v, ok := CommunityMap.Get(target); ok { 51 | c = v.(string) 52 | } 53 | gVersion := gsnmp.Version2c 54 | if v, ok := SnmpVersionMap.Get(target); ok { 55 | switch v { 56 | case "1": 57 | gVersion = gsnmp.Version1 58 | case "3": 59 | // TODO 60 | gVersion = gsnmp.Version3 61 | } 62 | } 63 | s := &gsnmp.GoSNMP{ 64 | Target: target, // eg:"172.18.0.2" 65 | Port: 161, 66 | Community: c, 67 | Version: gVersion, 68 | Timeout: time.Second * 3, 69 | Retries: 1, 70 | Transport: "udp", 71 | ExponentialTimeout: false, 72 | } 73 | er := s.Connect() 74 | defer func() { 75 | if er != nil { 76 | logrus.Warnf("try new snmp %s failed %v", target, er) 77 | } else { 78 | logrus.Infof("try new snmp %s success", target) 79 | } 80 | }() 81 | if er == nil { 82 | if _, er1 := SnmpWalk(s, "walk", OLibrary.IpForwarding); er1 != nil { 83 | er = er1 84 | } 85 | } 86 | if er != nil { 87 | s.Community = func(c string) string { 88 | if c == defaultCommunity { 89 | return defaultCommunityV2 90 | } else { 91 | return defaultCommunity 92 | } 93 | }(c) 94 | er = s.Connect() 95 | if er == nil { 96 | if _, er1 := SnmpWalk(s, "walk", OLibrary.IpForwarding); er1 != nil { 97 | er = er1 98 | } 99 | } 100 | if er != nil { 101 | TargetStatus.SetDefault(target, false) 102 | return s, er 103 | } 104 | } 105 | s.Timeout = time.Second * 15 106 | s.Retries = 1 107 | CommunityMap.SetDefault(target, s.Community) 108 | TargetStatus.SetDefault(target, true) 109 | return s, nil 110 | } 111 | 112 | func FetchItems(action, target string, oids []string, snmp *gsnmp.GoSNMP, args ...interface{}) (res []*SnmpResp, err error) { 113 | if len(oids) == 0 { 114 | return res, fmt.Errorf("%s", "oid is none") 115 | } 116 | key := fmt.Sprintf("%s-%s-%s", action, target, strings.Join(oids, "-")) 117 | if tmp, exist := ResultCache.Get(key); exist { 118 | return tmp.([]*SnmpResp), nil 119 | } 120 | if snmp == nil { 121 | snmp, err = NewSnmp(target) 122 | if err != nil { 123 | return 124 | } 125 | defer snmp.Conn.Close() 126 | } 127 | 128 | res, err = SnmpWalk(snmp, action, oids...) 129 | cacheTime := -2 130 | if len(args) > 0 { 131 | cacheTime = args[0].(int) 132 | } 133 | if err == nil { 134 | if cacheTime == -2 { 135 | if strings.HasSuffix(key, OLibrary.IpNetToMediaPhysAddress) { 136 | ResultCache.Set(key, res, time.Minute*30) 137 | } else { 138 | ResultCache.Set(key, res, time.Second*10) 139 | } 140 | } else if cacheTime > 0 { 141 | ResultCache.Set(key, res, time.Second*time.Duration(cacheTime)) 142 | } else { 143 | ResultCache.SetDefault(key, res) 144 | } 145 | } 146 | return 147 | } 148 | 149 | func SnmpCheck(hostName string, oids ...string) (string, bool) { 150 | cli, err := NewSnmp(hostName) 151 | if err != nil { 152 | //fmt.Println("snmp check failed:", hostName, err) 153 | return "", false 154 | } 155 | cli.Timeout = time.Second 156 | defer cli.Conn.Close() 157 | if len(oids) == 0 { 158 | res, er := SnmpWalk(cli, "walk", OLibrary.IpForwarding) 159 | if er != nil { 160 | return "", false 161 | } else if len(res) > 0 { 162 | return fmt.Sprintf("%v", res[0].DataValue), true 163 | } else { 164 | return "", true 165 | } 166 | } else { 167 | _, er := cli.Get(oids) 168 | return "", er == nil 169 | } 170 | } 171 | 172 | func GetNodeType(node string) NodeType { 173 | if nt, ok := IpNodeTypeMaps.Get(node); ok { 174 | return nt.(NodeType) 175 | } 176 | nt := NTOther 177 | defer func() { 178 | IpNodeTypeMaps.SetDefault(node, nt) 179 | }() 180 | // 只有mac地址,无法通过snmp获取数据,暂且标记为server 181 | if len(strings.Split(node, ".")) != 4 { 182 | nt = NTServer 183 | return nt 184 | } 185 | if v, supportSnmp := SnmpCheck(node); supportSnmp { 186 | if v == "2" { 187 | nt = NTServer 188 | } else { 189 | if _, ok := SnmpCheck(node, fmt.Sprintf("%s.0.0.0.0", OLibrary.IpRouteIfIndex)); ok { 190 | nt = NTRouter 191 | } else if _, ok := SnmpCheck(node, OLibrary.Dot1dBaseBridgeAddress); ok { 192 | nt = NTSwitch 193 | } 194 | } 195 | } else { 196 | if PingHost(node) { 197 | nt = NTServer 198 | } 199 | } 200 | return nt 201 | } 202 | 203 | // IpIdentGet 针对非服务器,获取标识ip, 204 | func IpIdentGet(node string, snmp *gsnmp.GoSNMP) (ip string, ips []string, err error) { 205 | ips = []string{} 206 | if v, ok := IpIdent.Get(node); ok { 207 | return v.(string), ips, nil 208 | } 209 | if snmp == nil { 210 | snmp, er := NewSnmp(node) 211 | if er != nil { 212 | err = er 213 | return 214 | } else { 215 | defer snmp.Conn.Close() 216 | } 217 | } 218 | r0, er := FetchItems("walk", node, []string{OLibrary.IpAdEntAddr}, snmp) 219 | if er != nil { 220 | err = er 221 | return 222 | } 223 | sort.Slice(r0, func(i, j int) bool { 224 | return InetAtoi(r0[i].DataValue.(string)) < InetAtoi(r0[j].DataValue.(string)) 225 | }) 226 | for _, v := range r0 { 227 | if v.DataValue.(string) == "127.0.0.1" || 228 | v.DataValue.(string) == "0.0.0.0" || 229 | v.DataValue.(string) == "127.0.0.0" { 230 | continue 231 | } 232 | ips = append(ips, v.DataValue.(string)) 233 | if _, er := NewSnmp(v.DataValue.(string)); er == nil { 234 | IpIdent.SetDefault(node, v.DataValue.(string)) 235 | ip = v.DataValue.(string) 236 | break 237 | } else if PingHost(v.DataValue.(string)) { 238 | IpIdent.SetDefault(node, v.DataValue.(string)) 239 | ip = v.DataValue.(string) 240 | continue 241 | } 242 | } 243 | return 244 | } 245 | 246 | func MD5(str string) string { 247 | h := md5.New() 248 | _, _ = io.WriteString(h, str) 249 | return hex.EncodeToString(h.Sum(nil)) 250 | } 251 | -------------------------------------------------------------------------------- /netTopology/lldp.md: -------------------------------------------------------------------------------- 1 | ### SNMP 获取LLDP信息前提 2 | - 目标开启LLDP功能 3 | ``` 4 | 在系统视图下执行lldp enable命令全局使能LLDP功能,缺省情况下,接口下的LLDP功能与全局下的LLDP功能状态保持一致。 5 | ``` 6 | 7 | - 开启MIB视图包含LLDP-MIB 8 | 9 | ``` 10 | 执行snmp-agent mib-view included iso-view iso命令创建包含所有MIB节点的MIB视图iso-view。 11 | 执行snmp-agent community { read | write } community-name mib-view iso-view命令配置MIB视图iso-view为网管使用的MIB视图。 12 | 执行snmp-agent sys-info version all命令配置交换机启用所有SNMP版本。 13 | ``` -------------------------------------------------------------------------------- /netTopology/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "os/signal" 6 | "syscall" 7 | 8 | "github.com/sirupsen/logrus" 9 | 10 | "netTopology/discover" 11 | . "netTopology/internal" 12 | ) 13 | 14 | func init() { 15 | // add your snmp communities, default is "public" or "publicv2" 16 | communities := map[string]string{} 17 | for k, v := range communities { 18 | CommunityMap.SetDefault(k, v) 19 | } 20 | } 21 | 22 | func main() { 23 | logrus.Info("start...") 24 | term := make(chan os.Signal, 1) 25 | signal.Notify(term, os.Interrupt, syscall.SIGTERM) 26 | md := discover.MasterDiscover("master") 27 | discover.NetworkMonitorInit(term, &md) 28 | <-term 29 | logrus.Info("Received SIGTERM, exiting ...") 30 | } 31 | -------------------------------------------------------------------------------- /secret/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | 8 | [dev-packages] 9 | 10 | [requires] 11 | python_version = "2.7" 12 | -------------------------------------------------------------------------------- /secret/README.md: -------------------------------------------------------------------------------- 1 | [English](README_en.md) 2 | ### 描述 3 | > 在我们的日常项目中,经常会遇得到一些敏感数据的存储问题,比如用户的密码,各种登录认证的token等等, 4 | > 如何安全的存储这些敏感信息十分重要。 5 | 6 | > 本工具是基于python实现的存储敏感数据方案,主要负责数据的加解密以及密钥维护等, 7 | > 包括inner和vault两个方法,你可以在项目中直接使用这些代码来进行敏感信息的处理, 8 | > 减少不必要的研究和开发。 9 | 10 | ### 两种方法 11 | #### inner 12 | > `inner.py` 脚本, 该方案最终使用中提供一个加密函数和一个解密函数函数,通过直接传递值进行加解密操作。 13 | > 用户在进行写入数据之前通过调用加密函数对数据加密,获取到加密数据以后,用户再根据自己场景进行存储。 14 | 15 | #### 使用示例 16 | ```python 17 | km = KeyManage() 18 | 19 | # init 20 | res1, ok1 = km.init() 21 | # Example 22 | t_plaintext = b"Hello, World!" # The plaintext to encrypt 23 | c = InnerCrypt() 24 | t_ciphertext, s1 = c.encrypt(t_plaintext) 25 | print("Ciphertext:", t_ciphertext) 26 | decrypted_plaintext, s2 = c.decrypt(t_ciphertext) 27 | print(decrypted_plaintext) 28 | print(s2) 29 | print("Decrypted plaintext:", decrypted_plaintext) 30 | print(".......") 31 | ``` 32 | 33 | #### vault 34 | > 本功能使用前提是需要首先部署第三方密码管理软件vault或者已经有了vault的服务, 35 | > 将敏感信息直接存储在第三方密码管理软件上,当前程序只需要配置服务地址以及令牌, 36 | > 然后执行增删改查的方法即可。 37 | 38 | ##### 使用前提 39 | 1. 启动了vault服务,创建一个kv密码引擎,同时启动一个transit密码引擎,如创建一个mount_path为cmdb的kv,同事启动一个transit引擎;也可以选择使用执行enable_secrets_engine来自动创建。 40 | 2. 程序侧配置vault的地址,以及调用api的X-Vault-Token 41 | 42 | ##### 使用示例 43 | ```python 44 | _base_url = "http://localhost:8200" 45 | _token = "your token" 46 | # Example 47 | sdk = VaultClient(_base_url, _token, mount_path="cmdb") 48 | 49 | _data = {"key1": "value1", "key2": "value2", "key3": "value3"} 50 | _path = "test001" 51 | _data = sdk.update(_path, _data, overwrite=True, encrypt=True) 52 | _data = sdk.read(_path, decrypt=True) 53 | ``` 54 | 55 | ### 其他使用场景 56 | > 如果您想在flask项目中使用它,可以参考开源项目[veops/cmdb](http://github.com/veops/cmdb)中 57 | > 关于密码部分的使用,包括flask command 和api中对相应的功能的调用 58 | -------------------------------------------------------------------------------- /secret/README_en.md: -------------------------------------------------------------------------------- 1 | [中文](README.md) 2 | ### Description 3 | > It is crucial to securely store these sensitive information in our daily projects, 4 | > such as user passwords, various login authentication tokens, and so on. Ensuring 5 | > the secure storage of these sensitive information is of utmost importance. 6 | 7 | ### Methods 8 | #### inner 9 | > the `inner.py` script. The final solution provides an encryption function and 10 | > a decryption function, which allow users to perform encryption and decryption 11 | > operations by directly passing the values. 12 | 13 | > Before writing data, users can call the encryption function to encrypt the data. 14 | > After obtaining the encrypted data, users can store it according to their own scenarios. 15 | 16 | ##### Example 17 | ```python 18 | km = KeyManage() 19 | 20 | # init 21 | res1, ok1 = km.init() 22 | # Example 23 | t_plaintext = b"Hello, World!" # The plaintext to encrypt 24 | c = InnerCrypt() 25 | t_ciphertext, s1 = c.encrypt(t_plaintext) 26 | print("Ciphertext:", t_ciphertext) 27 | decrypted_plaintext, s2 = c.decrypt(t_ciphertext) 28 | print(decrypted_plaintext) 29 | print(s2) 30 | print("Decrypted plaintext:", decrypted_plaintext) 31 | print(".......") 32 | ``` 33 | 34 | #### vault 35 | > The prerequisite for using this feature is to deploy a third-party 36 | > password management software like Vault or have an existing Vault service. 37 | > Sensitive information is stored directly in the third-party password 38 | > management software. 39 | 40 | > The current program only needs to configure the service address and token, 41 | > and then execute the methods for CRUD operations. 42 | 43 | ##### Prerequisites for usage 44 | 1. If you have started the Vault service, create a KV secret engine with a mount path of "cmdb" and start a Transit secret engine. Alternatively, you can choose to use the enable_secrets_engine command to automatically create them. 45 | 2. On the program side, configure the Vault address and provide the X-Vault-Token for API calls. 46 | 47 | ##### Example 48 | ```python 49 | _base_url = "http://localhost:8200" 50 | _token = "your token" 51 | # Example 52 | sdk = VaultClient(_base_url, _token, mount_path="cmdb") 53 | 54 | _data = {"key1": "value1", "key2": "value2", "key3": "value3"} 55 | _path = "test001" 56 | _data = sdk.update(_path, _data, overwrite=True, encrypt=True) 57 | _data = sdk.read(_path, decrypt=True) 58 | ``` 59 | 60 | ### Other Use cases 61 | > If you want to use it in a Flask project, you can refer to the usage of the password 62 | > section in the open-source project [veops/cmdb](http://github.com/veops/cmdb), 63 | > including the use of Flask commands and API calls for the corresponding functionality. 64 | -------------------------------------------------------------------------------- /secret/inner.py: -------------------------------------------------------------------------------- 1 | from base64 import b64encode, b64decode 2 | from colorama import Back 3 | from colorama import Fore 4 | from colorama import Style 5 | from colorama import init as colorama_init 6 | from cryptography.hazmat.backends import default_backend 7 | from cryptography.hazmat.primitives.ciphers import Cipher 8 | from cryptography.hazmat.primitives.ciphers import algorithms 9 | from cryptography.hazmat.primitives.ciphers import modes 10 | from cryptography.hazmat.primitives.ciphers.aead import AESGCM 11 | from cryptography.hazmat.primitives import hashes 12 | from cryptography.hazmat.primitives import padding 13 | from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC 14 | 15 | import os 16 | import secrets 17 | import sys 18 | from Cryptodome.Protocol.SecretSharing import Shamir 19 | 20 | # global_root_key just for test here 21 | global_root_key = "" 22 | global_encrypt_key = "" 23 | global_iv_length = 16 24 | global_key_shares = 5 # Number of generated key shares 25 | global_key_threshold = 3 # Minimum number of shares required to rebuild the key 26 | global_shares = [] 27 | 28 | backend_root_key_name = "root_key" 29 | backend_encrypt_key_name = "encrypt_key" 30 | backend_root_key_salt_name = "root_key_salt" 31 | backend_encrypt_key_salt_name = "encrypt_key_salt" 32 | success = "success" 33 | seal_status = True 34 | cache = {} 35 | 36 | 37 | def string_to_bytes(value): 38 | if isinstance(value, bytes): 39 | return value 40 | if sys.version_info.major == 2: 41 | byte_string = value 42 | else: 43 | byte_string = value.encode("utf-8") 44 | return byte_string 45 | 46 | 47 | class cache_backend: 48 | def __init__(self): 49 | pass 50 | 51 | @classmethod 52 | def get(cls, key): 53 | global cache 54 | return cache.get(key) 55 | 56 | @classmethod 57 | def add(cls, key, value): 58 | cache[key] = value 59 | return success, True 60 | 61 | @classmethod 62 | def update(cls, key, value): 63 | cache[key] = value 64 | return success, True 65 | 66 | 67 | class Backend: 68 | def __init__(self, backend=None): 69 | if not backend: 70 | self.backend = cache_backend 71 | else: 72 | self.backend = backend 73 | 74 | def get(self, key): 75 | return self.backend.get(key) 76 | 77 | def add(self, key, value): 78 | return self.backend.add(key, value) 79 | 80 | def update(self, key, value): 81 | return self.backend.update(key, value) 82 | 83 | 84 | class KeyManage: 85 | 86 | def __init__(self, trigger=None): 87 | self.trigger = trigger 88 | pass 89 | 90 | @classmethod 91 | def hash_root_key(cls, value): 92 | algorithm = hashes.SHA256() 93 | salt = Backend().get(backend_root_key_salt_name) 94 | if not salt: 95 | salt = secrets.token_hex(16) 96 | msg, ok = Backend().add(backend_root_key_salt_name, salt) 97 | if not ok: 98 | return msg, ok 99 | kdf = PBKDF2HMAC( 100 | algorithm=algorithm, 101 | length=32, 102 | salt=string_to_bytes(salt), 103 | iterations=100000, 104 | ) 105 | key = kdf.derive(string_to_bytes(value)) 106 | return b64encode(key).decode('utf-8'), True 107 | 108 | @classmethod 109 | def generate_encrypt_key(cls, key): 110 | algorithm = hashes.SHA256() 111 | salt = Backend().get(backend_encrypt_key_salt_name) 112 | if not salt: 113 | salt = secrets.token_hex(32) 114 | kdf = PBKDF2HMAC( 115 | algorithm=algorithm, 116 | length=32, 117 | salt=string_to_bytes(salt), 118 | iterations=100000, 119 | backend=default_backend() 120 | ) 121 | key = kdf.derive(string_to_bytes(key)) 122 | msg, ok = Backend().add(backend_encrypt_key_salt_name, salt) 123 | if ok: 124 | return b64encode(key).decode('utf-8'), ok 125 | else: 126 | return msg, ok 127 | 128 | @classmethod 129 | def generate_keys(cls, secret): 130 | shares = Shamir.split(global_key_threshold, global_key_shares, secret) 131 | new_shares = [] 132 | for share in shares: 133 | t = [i for i in share[1]] + [ord(i) for i in "{:0>2}".format(share[0])] 134 | new_shares.append(b64encode(bytes(t))) 135 | return new_shares 136 | 137 | def auth_root_secret(self, root_key): 138 | root_key_hash, ok = self.hash_root_key(b64encode(root_key)) 139 | if not ok: 140 | return { 141 | "message": root_key_hash, 142 | "status": "failed" 143 | } 144 | backend_root_key_hash = Backend().get(backend_root_key_name) 145 | if not backend_root_key_hash: 146 | return { 147 | "message": "should init firstly", 148 | "status": "failed" 149 | } 150 | elif backend_root_key_hash != root_key_hash: 151 | return { 152 | "message": "invalid unseal keys", 153 | "status": "failed" 154 | } 155 | encrypt_key_aes = Backend().get(backend_encrypt_key_name) 156 | if not encrypt_key_aes: 157 | return { 158 | "message": "encrypt key is empty", 159 | "status": "failed" 160 | } 161 | global global_encrypt_key 162 | global global_root_key 163 | global global_shares 164 | global_encrypt_key = InnerCrypt.aes_decrypt(string_to_bytes(root_key), encrypt_key_aes) 165 | global_root_key = root_key 166 | global_shares = [] 167 | return { 168 | "message": success, 169 | "status": success 170 | } 171 | 172 | def unseal(self, key): 173 | if not self.is_seal(): 174 | return { 175 | "message": "current status is unseal, skip", 176 | "status": "skip" 177 | } 178 | global global_shares, global_root_key, global_encrypt_key 179 | t = [i for i in b64decode(key)] 180 | global_shares.append((int("".join([chr(i) for i in t[-2:]])), bytes(t[:-2]))) 181 | if len(global_shares) >= global_key_threshold: 182 | recovered_secret = Shamir.combine(global_shares[:global_key_threshold]) 183 | return self.auth_root_secret(recovered_secret) 184 | else: 185 | return { 186 | "Process": "{0}/{1}".format(len(global_shares), global_key_threshold), 187 | "message": "waiting for inputting other unseal key", 188 | "status": "waiting" 189 | } 190 | 191 | def generate_unseal_keys(self): 192 | info = Backend().get(backend_root_key_name) 193 | if info: 194 | return "already exist", [], False 195 | secret = AESGCM.generate_key(128) 196 | shares = self.generate_keys(secret) 197 | return b64encode(secret), shares, True 198 | 199 | def init(self): 200 | """ 201 | init the master key, unseal key and store in backend 202 | :return: 203 | """ 204 | root_key = Backend().get(backend_root_key_name) 205 | if root_key: 206 | return {"message": "already init, skip"}, False 207 | else: 208 | root_key, shares, status = self.generate_unseal_keys() 209 | if not status: 210 | return {"message": root_key}, False 211 | # hash root key and store in backend 212 | root_key_hash, ok = self.hash_root_key(root_key) 213 | if not ok: 214 | return {"message": root_key_hash}, False 215 | msg, ok = Backend().add(backend_root_key_name, root_key_hash) 216 | if not ok: 217 | return {"message": msg}, False 218 | # generate encrypt key from root_key and store in backend 219 | encrypt_key, ok = self.generate_encrypt_key(root_key) 220 | if not ok: 221 | return {"message": encrypt_key} 222 | encrypt_key_aes = InnerCrypt.aes_encrypt(root_key, encrypt_key) 223 | msg, ok = Backend().add(backend_encrypt_key_name, encrypt_key_aes) 224 | if not ok: 225 | return {"message": msg}, False 226 | # 227 | global global_root_key, global_encrypt_key 228 | global_root_key = root_key 229 | global_encrypt_key = encrypt_key 230 | self.print_token(shares, root_token=root_key) 231 | return {"message": "OK", 232 | "details": { 233 | "root_token": root_key, 234 | "seal_tokens": shares, 235 | }}, True 236 | 237 | def auto_unseal(self): 238 | if not self.trigger: 239 | return "trigger config is empty, skip", False 240 | if self.trigger.startswith("http"): 241 | pass 242 | # TODO 243 | elif len(self.trigger.strip()) == 24: 244 | res = self.auth_root_secret(self.trigger) 245 | if res.get("status") == success: 246 | return success, True 247 | else: 248 | return res.get("message"), False 249 | else: 250 | return "trigger config is invalid, skip", False 251 | 252 | def seal(self, root_key): 253 | root_key_hash, ok = self.hash_root_key(b64encode(root_key)) 254 | if not ok: 255 | return root_key_hash, ok 256 | backend_root_key_hash = Backend().get(backend_root_key_name) 257 | if not backend_root_key_hash: 258 | return "not init, seal skip", False 259 | else: 260 | global global_root_key 261 | global global_encrypt_key 262 | global_root_key = "" 263 | global_encrypt_key = "" 264 | return success, True 265 | 266 | @classmethod 267 | def is_seal(cls): 268 | """ 269 | If there is no initialization or the root key is inconsistent, it is considered to be in a sealed state. 270 | :return: 271 | """ 272 | root_key = Backend().get(backend_root_key_name) 273 | if root_key == "" or root_key != global_root_key: 274 | return "invalid root key", True 275 | return "", False 276 | 277 | @classmethod 278 | def print_token(cls, shares, root_token): 279 | """ 280 | data: {"message": "OK", 281 | "details": { 282 | "root_token": root_key, 283 | "seal_tokens": shares, 284 | }} 285 | """ 286 | colorama_init() 287 | print(Style.BRIGHT, "Please be sure to store the Unseal Key in a secure location and avoid losing it." 288 | " The Unseal Key is required to unseal the system every time when it restarts." 289 | " Successful unsealing is necessary to enable the password feature.") 290 | for i, v in enumerate(shares): 291 | print( 292 | Fore.RED + Back.LIGHTRED_EX + "unseal token " + str(i + 1) + ": " + v.decode("utf-8") + Style.RESET_ALL) 293 | print() 294 | print(Fore.GREEN + "root token: " + root_token.decode("utf-8") + Style.RESET_ALL) 295 | 296 | 297 | class InnerCrypt: 298 | def __init__(self, trigger=None): 299 | self.encrypt_key = b64decode(global_encrypt_key.encode("utf-8")) 300 | 301 | def encrypt(self, plaintext): 302 | """ 303 | encrypt method contain aes currently 304 | """ 305 | return self.aes_encrypt(self.encrypt_key, plaintext) 306 | 307 | def decrypt(self, ciphertext): 308 | """ 309 | decrypt method contain aes currently 310 | """ 311 | return self.aes_decrypt(self.encrypt_key, ciphertext) 312 | 313 | @classmethod 314 | def aes_encrypt(cls, key, plaintext): 315 | if isinstance(plaintext, str): 316 | plaintext = string_to_bytes(plaintext) 317 | iv = os.urandom(global_iv_length) 318 | try: 319 | cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend()) 320 | encryptor = cipher.encryptor() 321 | v_padder = padding.PKCS7(algorithms.AES.block_size).padder() 322 | padded_plaintext = v_padder.update(plaintext) + v_padder.finalize() 323 | ciphertext = encryptor.update(padded_plaintext) + encryptor.finalize() 324 | return b64encode(iv + ciphertext).decode("utf-8"), True 325 | except Exception as e: 326 | return str(e), False 327 | 328 | @classmethod 329 | def aes_decrypt(cls, key, ciphertext): 330 | try: 331 | s = b64decode(ciphertext.encode("utf-8")) 332 | iv = s[:global_iv_length] 333 | ciphertext = s[global_iv_length:] 334 | cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend()) 335 | decrypter = cipher.decryptor() 336 | decrypted_padded_plaintext = decrypter.update(ciphertext) + decrypter.finalize() 337 | unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder() 338 | plaintext = unpadder.update(decrypted_padded_plaintext) + unpadder.finalize() 339 | return plaintext.decode('utf-8'), True 340 | except Exception as e: 341 | return str(e), False 342 | 343 | 344 | 345 | if __name__ == "__main__": 346 | 347 | km = KeyManage() 348 | 349 | # init 350 | res1, ok1 = km.init() 351 | # Example 352 | t_plaintext = b"Hello, World!" # The plaintext to encrypt 353 | c = InnerCrypt() 354 | t_ciphertext, s1 = c.encrypt(t_plaintext) 355 | print("-"*30) 356 | print("Ciphertext:", t_ciphertext) 357 | decrypted_plaintext, s2 = c.decrypt(t_ciphertext) 358 | print("Decrypted plaintext:", decrypted_plaintext) 359 | -------------------------------------------------------------------------------- /secret/vault.py: -------------------------------------------------------------------------------- 1 | from base64 import b64decode 2 | from base64 import b64encode 3 | 4 | import hvac 5 | 6 | 7 | class VaultClient: 8 | def __init__(self, base_url, token, mount_path='cmdb'): 9 | self.client = hvac.Client(url=base_url, token=token) 10 | self.mount_path = mount_path 11 | 12 | def create_app_role(self, role_name, policies): 13 | resp = self.client.create_approle(role_name, policies=policies) 14 | 15 | return resp == 200 16 | 17 | def delete_app_role(self, role_name): 18 | resp = self.client.delete_approle(role_name) 19 | 20 | return resp == 204 21 | 22 | def update_app_role_policies(self, role_name, policies): 23 | resp = self.client.update_approle_role(role_name, policies=policies) 24 | 25 | return resp == 204 26 | 27 | def get_app_role(self, role_name): 28 | resp = self.client.get_approle(role_name) 29 | resp.json() 30 | if resp.status_code == 200: 31 | return resp.json 32 | else: 33 | return {} 34 | 35 | def enable_secrets_engine(self): 36 | resp = self.client.sys.enable_secrets_engine('kv', path=self.mount_path) 37 | resp_01 = self.client.sys.enable_secrets_engine('transit') 38 | 39 | if resp.status_code == 200 and resp_01.status_code == 200: 40 | return resp.json 41 | else: 42 | return {} 43 | 44 | def encrypt(self, plaintext): 45 | response = self.client.secrets.transit.encrypt_data(name='transit-key', plaintext=plaintext) 46 | ciphertext = response['data']['ciphertext'] 47 | 48 | return ciphertext 49 | 50 | # decrypt data 51 | def decrypt(self, ciphertext): 52 | response = self.client.secrets.transit.decrypt_data(name='transit-key', ciphertext=ciphertext) 53 | plaintext = response['data']['plaintext'] 54 | 55 | return plaintext 56 | 57 | def write(self, path, data, encrypt=None): 58 | if encrypt: 59 | for k, v in data.items(): 60 | data[k] = self.encrypt(self.encode_base64(v)) 61 | response = self.client.secrets.kv.v2.create_or_update_secret( 62 | path=path, 63 | secret=data, 64 | mount_point=self.mount_path 65 | ) 66 | 67 | return response 68 | 69 | # read data 70 | def read(self, path, decrypt=True): 71 | try: 72 | response = self.client.secrets.kv.v2.read_secret_version( 73 | path=path, raise_on_deleted_version=False, mount_point=self.mount_path 74 | ) 75 | except Exception as e: 76 | return str(e), False 77 | data = response['data']['data'] 78 | if decrypt: 79 | try: 80 | for k, v in data.items(): 81 | data[k] = self.decode_base64(self.decrypt(v)) 82 | except: 83 | return data, True 84 | 85 | return data, True 86 | 87 | # update data 88 | def update(self, path, data, overwrite=True, encrypt=True): 89 | if encrypt: 90 | for k, v in data.items(): 91 | data[k] = self.encrypt(self.encode_base64(v)) 92 | if overwrite: 93 | response = self.client.secrets.kv.v2.create_or_update_secret( 94 | path=path, 95 | secret=data, 96 | mount_point=self.mount_path 97 | ) 98 | else: 99 | response = self.client.secrets.kv.v2.patch(path=path, secret=data, mount_point=self.mount_path) 100 | 101 | return response 102 | 103 | # delete data 104 | def delete(self, path): 105 | response = self.client.secrets.kv.v2.delete_metadata_and_all_versions( 106 | path=path, 107 | mount_point=self.mount_path 108 | ) 109 | 110 | return response 111 | 112 | # Base64 encode 113 | @classmethod 114 | def encode_base64(cls, data): 115 | encoded_bytes = b64encode(data.encode()) 116 | encoded_string = encoded_bytes.decode() 117 | 118 | return encoded_string 119 | 120 | # Base64 decode 121 | @classmethod 122 | def decode_base64(cls, encoded_string): 123 | decoded_bytes = b64decode(encoded_string) 124 | decoded_string = decoded_bytes.decode() 125 | 126 | return decoded_string 127 | 128 | 129 | if __name__ == "__main__": 130 | _base_url = "http://localhost:8300" 131 | _token = "hvs.LiCunLUksihFxCLlHsWvHTWo" 132 | 133 | _path = "test001" 134 | # Example 135 | sdk = VaultClient(_base_url, _token, mount_path="test") 136 | sdk.enable_secrets_engine() 137 | # sdk.enable_secrets_engine() 138 | _data = {"key1": "value1", "key2": "value2", "key3": "value3"} 139 | _data = sdk.update(_path, _data, overwrite=True, encrypt=True) 140 | print(_data) 141 | _data = sdk.read(_path, decrypt=True) 142 | print(_data) 143 | 144 | -------------------------------------------------------------------------------- /zabbix2tsdb/go.mod: -------------------------------------------------------------------------------- 1 | module zabbix 2 | 3 | go 1.17 4 | 5 | require github.com/spf13/pflag v1.0.5 6 | -------------------------------------------------------------------------------- /zabbix2tsdb/go.sum: -------------------------------------------------------------------------------- 1 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 2 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 3 | -------------------------------------------------------------------------------- /zabbix2tsdb/readme.md: -------------------------------------------------------------------------------- 1 | tools for converting zabbix data to tsdb format, the new output contain metric,labels,value and timestamp 2 | # output 3 | ## influxdb 4 | ```shell 5 | sys_cpu_idle,core=cpu1,env=prod {V}=12.34 1690892492000000000 6 | ``` 7 | 8 | ## prometheus 9 | > output as follows 10 | ```shell 11 | system_cpu_idle{core="cpu1",env="prod"} 12.34 1690892492000000000 12 | ``` 13 | 14 | # how to use 15 | ```shell 16 | -k, --acceptKeys strings 需要同步的Key,通配符匹配(*). 例如:如需要同步'system.'开头的key_,则配置'system.*' (default [system.*]) 17 | -a, --address string zabbix api请求地址。为防止带宽占用以及网络延迟问题,我们强烈推荐将节点选择在与zabbix api 18 | 服务在同一台机器上。如 http://127.0.0.1:8080/api_jsonrpc.php,可以写完整地址,也可以直接省去后缀(api_jsonrpc.php), 19 | 如写http://127.0.0.1:8080 20 | -v, --apiVersion string api版本 (default "2.0") 21 | -c, --cluster string zabbix集群名称, 当采集多个zabbix集群,且不同集群存在相同的主机名(ip),可以避免数据混乱 (default "0") 22 | -f, --dataFormat string data format that you want to convert to, you can choose 'prometheus' or 'influxdb', (default "influxdb") 23 | -g, --groups strings 需要同步的group分组 (default [Linux servers,Zabbix servers,Virtual machines]) 24 | -i, --interval int 同步时间间隔,单位秒. 防止对zabbix服务器造成太大压力,系统允许的最小时间间隔为30秒 (default 60) 25 | -p, --password string 允许通过api访问数据的用户对应的密码,推荐使用环境变量 (default "zabbix") 26 | -u, --user string 允许通过api访问数据的用户名, 推荐使用环境变量 (default "Admin") 27 | ``` -------------------------------------------------------------------------------- /zabbix2tsdb/zabbix.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "fmt" 8 | "io" 9 | "io/ioutil" 10 | "math" 11 | "net/http" 12 | "os" 13 | "os/signal" 14 | "regexp" 15 | "strings" 16 | "sync" 17 | "syscall" 18 | "time" 19 | 20 | "github.com/spf13/pflag" 21 | ) 22 | 23 | const ( 24 | MaxIdleConnections int = 20 25 | RequestTimeout int = 30 26 | ApiSuffix = "api_jsonrpc.php" 27 | defaultKey = "{V}" 28 | ) 29 | 30 | func Init() { 31 | KeyPattern = regexp.MustCompile(`^([0-9a-zA-Z_.]*)\\[(.*)\\]$`) 32 | ReplMetricPattern = regexp.MustCompile(`([^a-zA-Z0-9:_]+?)`) 33 | ReplParamsPattern = regexp.MustCompile(`([," =]+?)`) 34 | //for _, v := range zabbixConfig.AccetpKeys{ 35 | // if p, er := regexp.Compile(v); er == nil{ 36 | // AcceptKeysPattern = append(AcceptKeysPattern, p) 37 | // } 38 | //} 39 | zabbixConfig.Cluster = ReplParamsPattern.ReplaceAllString(zabbixConfig.Cluster, "_") 40 | address := zabbixConfig.Address 41 | if !strings.HasSuffix(address, ApiSuffix) { 42 | zabbixConfig.Address = fmt.Sprintf("%s/%s", strings.TrimSuffix(address, "/"), ApiSuffix) 43 | } 44 | zApi = NewZabbixClient() 45 | signal.Notify(signals, os.Interrupt, syscall.SIGTERM) 46 | } 47 | 48 | type Host struct { 49 | Locker sync.RWMutex 50 | Ids []string 51 | } 52 | 53 | type Config struct { 54 | Groups map[string]struct{} 55 | Cluster string 56 | Address string 57 | User string 58 | Password string 59 | ApiVersion string 60 | AccetpKeys []string 61 | Interval int64 62 | DataFormat string 63 | } 64 | 65 | var ( 66 | zabbixConfig = Config{Groups: map[string]struct{}{}} 67 | zApi *ZabbixApi 68 | signals = make(chan os.Signal, 1) 69 | // hostIdHost 通过item.get获取到hostId之后,需要查找对应的host 70 | hostIdHost sync.Map 71 | // globalHostIds 之所以使用map,是因为多个groupId可能包含相同的hostId 72 | //globalHostIds sync.Map 73 | hosts = Host{Locker: sync.RWMutex{}} 74 | groupNameId sync.Map 75 | 76 | // KeyPattern key_中提取metric,params 77 | KeyPattern *regexp.Regexp 78 | // ReplMetricPattern metric名称转换 79 | ReplMetricPattern *regexp.Regexp 80 | // ReplParamsPattern 用来转换params,hostName 81 | ReplParamsPattern *regexp.Regexp 82 | AcceptKeysPattern []*regexp.Regexp 83 | 84 | ConnectError = fmt.Errorf("connectError") 85 | 86 | // hostQueryBatch 批量查询host采集项时,控制一次性查询量,防止数据过多 87 | hostQueryBatch int = 150 88 | ) 89 | 90 | func (h *Host) Set(hostIds map[string]struct{}) { 91 | tmp := make(map[string]struct{}) 92 | h.Locker.Lock() 93 | defer h.Locker.Unlock() 94 | // delete expired hostId 95 | for i := 0; i < len(h.Ids); i++ { 96 | if _, ok := hostIds[h.Ids[i]]; !ok { 97 | h.Ids = append(h.Ids[:i], h.Ids[i+1:]...) 98 | i-- 99 | } else { 100 | tmp[h.Ids[i]] = struct{}{} 101 | } 102 | } 103 | // add host 104 | for hostId := range hostIds { 105 | if _, ok := tmp[hostId]; !ok { 106 | h.Ids = append(h.Ids, hostId) 107 | } 108 | } 109 | } 110 | 111 | // createHTTPClient for connection re-use 112 | func createHTTPClient() *http.Client { 113 | client := &http.Client{ 114 | Transport: &http.Transport{ 115 | MaxConnsPerHost: MaxIdleConnections, 116 | }, 117 | Timeout: time.Duration(RequestTimeout) * time.Second, 118 | } 119 | return client 120 | } 121 | 122 | type ZabbixApi struct { 123 | User string 124 | Password string 125 | Address string 126 | ApiVersion string 127 | Auth string 128 | Client *http.Client 129 | } 130 | 131 | func NewZabbixClient() *ZabbixApi { 132 | c := createHTTPClient() 133 | return &ZabbixApi{ 134 | User: zabbixConfig.User, 135 | Password: zabbixConfig.Password, 136 | Address: zabbixConfig.Address, 137 | ApiVersion: zabbixConfig.ApiVersion, 138 | Client: c, 139 | } 140 | } 141 | 142 | func (z *ZabbixApi) Login() error { 143 | r, er := json.Marshal(map[string]interface{}{ 144 | "jsonrpc": z.ApiVersion, 145 | "method": "user.login", 146 | "params": map[string]string{ 147 | "user": z.User, 148 | "password": z.Password, 149 | }, 150 | "id": 1, 151 | }) 152 | if er != nil { 153 | return er 154 | } 155 | body := bytes.NewBuffer(r) 156 | req, er := http.NewRequest("GET", z.Address, body) 157 | if er != nil { 158 | return er 159 | } 160 | req.Header.Set("Content-Type", "application/json-rpc") 161 | resp, er := z.Client.Do(req) 162 | if er != nil { 163 | return ConnectError 164 | } 165 | defer resp.Body.Close() 166 | result, er := io.ReadAll(resp.Body) 167 | if er != nil { 168 | return er 169 | } 170 | respData := make(map[string]interface{}) 171 | er = json.Unmarshal(result, &respData) 172 | if er != nil { 173 | return er 174 | } 175 | if auth, ok := respData["result"]; ok { 176 | z.Auth = auth.(string) 177 | } 178 | if z.Auth == "" { 179 | return fmt.Errorf("获取认证秘钥为空") 180 | } 181 | return nil 182 | } 183 | 184 | func (z *ZabbixApi) GroupIds() error { 185 | r, _ := json.Marshal(map[string]interface{}{ 186 | "jsonrpc": z.ApiVersion, 187 | "method": "hostgroup.get", 188 | "params": map[string]interface{}{ 189 | "output": []string{"name", "groupid"}, 190 | }, 191 | "auth": z.Auth, 192 | "id": 1, 193 | }) 194 | body := bytes.NewBuffer(r) 195 | req, er := http.NewRequest("GET", z.Address, body) 196 | if er != nil { 197 | return er 198 | } 199 | req.Header.Set("Content-Type", "application/json-rpc") 200 | resp, er := z.Client.Do(req) 201 | if er != nil { 202 | return ConnectError 203 | } 204 | defer resp.Body.Close() 205 | result, er := io.ReadAll(resp.Body) 206 | if er != nil { 207 | return er 208 | } 209 | respData := make(map[string]interface{}) 210 | er = json.Unmarshal(result, &respData) 211 | if er != nil { 212 | return er 213 | } 214 | if result, ok := respData["result"]; ok { 215 | for _, v := range result.([]interface{}) { 216 | groupNameId.Store(v.(map[string]interface{})["name"].(string), 217 | v.(map[string]interface{})["groupid"].(string)) 218 | } 219 | } 220 | return nil 221 | } 222 | 223 | func (z *ZabbixApi) HostIds(groupIds []string) error { 224 | r, er := json.Marshal(map[string]interface{}{ 225 | "jsonrpc": z.ApiVersion, 226 | "method": "host.get", 227 | "params": map[string]interface{}{ 228 | "output": []string{"hostid", "host"}, 229 | //"output": "extend", 230 | "groupids": groupIds, 231 | }, 232 | "auth": z.Auth, 233 | "id": 1, 234 | }) 235 | if er != nil { 236 | return er 237 | } 238 | body := bytes.NewBuffer(r) 239 | req, er := http.NewRequest("GET", z.Address, body) 240 | if er != nil { 241 | return er 242 | } 243 | req.Header.Set("Content-Type", "application/json-rpc") 244 | resp, er := z.Client.Do(req) 245 | if er != nil { 246 | return er 247 | } 248 | defer resp.Body.Close() 249 | result, er := ioutil.ReadAll(resp.Body) 250 | if er != nil { 251 | return er 252 | } 253 | respData := make(map[string]interface{}) 254 | er = json.Unmarshal(result, &respData) 255 | if er != nil { 256 | return er 257 | } 258 | tmp := map[string]struct{}{} 259 | if result, ok := respData["result"]; ok { 260 | for _, v := range result.([]interface{}) { 261 | if hostId, ok := v.(map[string]interface{})["hostid"]; ok { 262 | tmp[hostId.(string)] = struct{}{} 263 | if host, ok := v.(map[string]interface{})["host"]; ok { 264 | if _, ok := hostIdHost.Load(host); !ok { 265 | hostIdHost.Store(hostId, host) 266 | } 267 | } 268 | } 269 | } 270 | } 271 | hosts.Set(tmp) 272 | return nil 273 | } 274 | 275 | func (z *ZabbixApi) SmartItems(hostIds []string) { 276 | var hostIdsGroups [][]string 277 | groupNumber := int(math.Ceil(float64(len(hostIds)) / float64(hostQueryBatch))) 278 | for i := 0; i < groupNumber; i++ { 279 | startIndex := i * hostQueryBatch 280 | endIndex := (i + 1) * hostQueryBatch 281 | if endIndex > len(hostIds) { 282 | endIndex = len(hostIds) 283 | } 284 | hostIdsGroups = append(hostIdsGroups, hostIds[startIndex:endIndex]) 285 | } 286 | for _, subHostIds := range hostIdsGroups { 287 | er := z.Items(subHostIds) 288 | if er != nil { 289 | if er == ConnectError { 290 | _ = z.Login() 291 | } 292 | } 293 | } 294 | 295 | } 296 | 297 | func (z *ZabbixApi) Items(hostIds []string) error { 298 | r, er := json.Marshal(map[string]interface{}{ 299 | "jsonrpc": z.ApiVersion, 300 | "method": "item.get", 301 | "params": map[string]interface{}{ 302 | //"output": []string{"hostid"}, 303 | "output": []string{"key_", "hostid", "lastvalue", "lastclock", "value_type"}, 304 | "hostids": hostIds, 305 | "search": map[string]interface{}{"key_": zabbixConfig.AccetpKeys}, 306 | "searchWildcardsEnabled": true, 307 | "searchByAny": true, 308 | }, 309 | "auth": z.Auth, 310 | "id": 1, 311 | }) 312 | if er != nil { 313 | return er 314 | } 315 | body := bytes.NewBuffer(r) 316 | req, er := http.NewRequest("GET", z.Address, body) 317 | if er != nil { 318 | return er 319 | } 320 | req.Header.Set("Content-Type", "application/json-rpc") 321 | resp, er := z.Client.Do(req) 322 | if er != nil { 323 | return ConnectError 324 | } 325 | defer resp.Body.Close() 326 | result, er := io.ReadAll(resp.Body) 327 | if er != nil { 328 | return er 329 | } 330 | respData := make(map[string]interface{}) 331 | er = json.Unmarshal(result, &respData) 332 | if er != nil { 333 | return er 334 | } 335 | if result, ok := respData["result"]; ok { 336 | for _, v := range result.([]interface{}) { 337 | er := processMetric(v.(map[string]interface{})) 338 | if er != nil { 339 | _, _ = fmt.Fprintln(os.Stderr, er.Error()) 340 | } 341 | } 342 | } 343 | return nil 344 | } 345 | 346 | //func isAcceptedKey(key string) bool { 347 | // for _, p := range AcceptKeysPattern { 348 | // if p.MatchString(key) { 349 | // return true 350 | // } 351 | // } 352 | // return false 353 | //} 354 | 355 | func processMetric(item map[string]interface{}) error { 356 | dataStr := "" 357 | // only numeric/float values can convert to prometheus 358 | //dataStr := "%s,c=%s,__endpoint__=%s,params=%s {val}=%s %s000000000" 359 | if item["value_type"] == "0" || item["value_type"].(string) == "3" { 360 | if _, ok := item["key_"]; !ok { 361 | return fmt.Errorf("no key_") 362 | } 363 | // step 1: metric 364 | //if !isAcceptedKey(item["key_"].(string)){ 365 | // return nil 366 | //} 367 | res := KeyPattern.FindStringSubmatch(item["key_"].(string)) 368 | if len(res) > 1 { 369 | dataStr = ReplMetricPattern.ReplaceAllString(res[1], "_") 370 | } else { 371 | dataStr = ReplMetricPattern.ReplaceAllString(item["key_"].(string), "_") 372 | } 373 | // step 2 tags: zabbix cluster 374 | dataStr = fmt.Sprintf("%s,c=%s", dataStr, zabbixConfig.Cluster) 375 | // step3 tags: hostName 376 | if hostName, ok := hostIdHost.Load(item["hostid"].(string)); ok { 377 | dataStr = fmt.Sprintf("%s,__endpoint__=%s", 378 | dataStr, ReplParamsPattern.ReplaceAllString(hostName.(string), "_")) 379 | } 380 | // step 4 tags: params 381 | if len(res) >= 2 && res[0] != "" { 382 | dataStr = fmt.Sprintf("%s,p=%s", dataStr, ReplParamsPattern.ReplaceAllString(res[2], "\\$1")) 383 | } 384 | // step5 处理值 385 | if _, ok := item["lastvalue"]; !ok { 386 | return nil 387 | } 388 | dataStr = fmt.Sprintf("%s %s=%v", dataStr, defaultKey, item["lastvalue"]) 389 | // step5 处理时间 390 | if _, ok := item["lastclock"]; ok && len(item["lastclock"].(string)) == 10 { 391 | dataStr = fmt.Sprintf(" %s %v000000000", dataStr, item["lastclock"]) 392 | } else { 393 | return nil 394 | } 395 | } 396 | if dataStr != "" { 397 | if zabbixConfig.DataFormat == "prometheus" { 398 | s := convertInfluxToPrometheus(dataStr) 399 | for _, v := range s { 400 | fmt.Println(v) 401 | } 402 | } else { 403 | fmt.Println(dataStr) 404 | } 405 | } 406 | return nil 407 | } 408 | 409 | func convertInfluxToPrometheus(influxData string) []string { 410 | var data []string 411 | splitData := strings.Split(influxData, " ") 412 | measurementTagSet := strings.Split(splitData[0], ",") 413 | measurement := measurementTagSet[0] 414 | tagSet := measurementTagSet[1:] 415 | 416 | fieldSet := strings.Split(splitData[1], ",") 417 | var timestamp string 418 | if len(splitData) == 3 { 419 | timestamp = splitData[2] 420 | } 421 | 422 | labelSet := make([]string, len(tagSet)) 423 | var t []string 424 | for i, tag := range tagSet { 425 | t = strings.SplitN(tag, "=", 2) 426 | if len(t) == 2 { 427 | labelSet[i] = fmt.Sprintf("%s=\"%s\"", t[0], t[1]) 428 | } 429 | } 430 | for _, val := range fieldSet { 431 | t = strings.SplitN(val, "=", 2) 432 | if len(t) == 2 { 433 | if t[0] != defaultKey { 434 | measurement = fmt.Sprintf(`%s_%s`, measurement, t[0]) 435 | } 436 | data = append(data, fmt.Sprintf(`%s{%s} %s %s`, measurement, strings.Join(labelSet, ","), t[1], timestamp)) 437 | } 438 | } 439 | return data 440 | } 441 | 442 | func configParse() { 443 | //var groups []string 444 | pflag.StringVarP(&zabbixConfig.Cluster, "cluster", "c", "0", 445 | "zabbix集群名称, 当采集多个zabbix集群,且不同集群存在相同的主机名(ip),可以避免数据混乱") 446 | pflag.StringVarP(&zabbixConfig.Address, "address", "a", "", 447 | fmt.Sprintf(`zabbix api请求地址。为防止带宽占用以及网络延迟问题,我们强烈推荐将节点选择在与zabbix api 448 | 服务在同一台机器上。如 http://127.0.0.1:8080/%[1]s,可以写完整地址,也可以直接省去后缀(%[1]s), 449 | 如写http://127.0.0.1:8080`, ApiSuffix)) 450 | pflag.StringVarP(&zabbixConfig.User, "user", "u", "Admin", "允许通过api访问数据的用户名, 推荐使用环境变量") 451 | pflag.StringVarP(&zabbixConfig.Password, "password", "p", "zabbix", "允许通过api访问数据的用户对应的密码,推荐使用环境变量") 452 | groups := pflag.StringSliceP("groups", "g", 453 | []string{"Linux servers", "Zabbix servers", "Virtual machines"}, "需要同步的group分组") 454 | pflag.StringVarP(&zabbixConfig.ApiVersion, "apiVersion", "v", "2.0", "api版本") 455 | //pflag.StringSliceVarP(&zabbixConfig.AccetpKeys, "acceptKeys", "k", 456 | // []string{"system\\..*"}, "需要同步的Key,正则匹配") 457 | pflag.StringSliceVarP(&zabbixConfig.AccetpKeys, "acceptKeys", "k", 458 | []string{"system.*"}, "需要同步的Key,通配符匹配(*). 例如:如需要同步'system.'开头的key_,则配置'system.*'") 459 | pflag.Int64VarP(&zabbixConfig.Interval, "interval", "i", int64(60), 460 | "同步时间间隔,单位秒. 防止对zabbix服务器造成太大压力,系统允许的最小时间间隔为30秒") 461 | pflag.StringVarP(&zabbixConfig.DataFormat, "dataFormat", "f", "influxdb", 462 | "data format that you want to convert to, you can choose 'prometheus' or 'influxdb', default is influxdb") 463 | pflag.Parse() 464 | if zabbixConfig.Address == "" { 465 | _, _ = fmt.Fprintln(os.Stderr, "address 不能为空,我们推荐防止带宽占用,将节点选择在与zabbix api 服务在同一台机器上") 466 | os.Exit(0) 467 | } 468 | if len(*groups) > 0 { 469 | for _, group := range *groups { 470 | zabbixConfig.Groups[group] = struct{}{} 471 | } 472 | } 473 | if zabbixConfig.Interval < 30 { 474 | zabbixConfig.Interval = 30 475 | } 476 | if s := os.Getenv("password"); s != "" { 477 | zabbixConfig.Password = s 478 | } 479 | if s := os.Getenv("user"); s != "" { 480 | zabbixConfig.User = s 481 | } 482 | } 483 | 484 | func updateGroupAndHost() { 485 | er := zApi.GroupIds() 486 | if er != nil { 487 | //_, _ = fmt.Fprintln(os.Stderr, fmt.Sprintf("update groups failed:%s", er)) 488 | _, _ = fmt.Fprintf(os.Stderr, "update groups failed:%s\n", er) 489 | // login again 490 | er := zApi.Login() 491 | if er != nil { 492 | _, _ = fmt.Fprintf(os.Stderr, "login failed:%s\n", er) 493 | } 494 | } 495 | groupIds := []string{} 496 | for groupName := range zabbixConfig.Groups { 497 | if groupId, ok := groupNameId.Load(groupName); ok { 498 | groupIds = append(groupIds, groupId.(string)) 499 | } 500 | } 501 | if len(groupIds) == 0 { 502 | return 503 | } 504 | er = zApi.HostIds(groupIds) 505 | if er != nil { 506 | _, _ = fmt.Fprintf(os.Stderr, "update hostid failed:%s\n", er) 507 | // login again 508 | er := zApi.Login() 509 | if er != nil { 510 | _, _ = fmt.Fprintf(os.Stderr, "login failed:%s\n", er) 511 | } 512 | } 513 | } 514 | 515 | func GetItems() { 516 | if len(hosts.Ids) == 0 { 517 | return 518 | } 519 | zApi.SmartItems(hosts.Ids) 520 | } 521 | 522 | func Loop(ctx context.Context) { 523 | updateGroupAndHost() 524 | GetItems() 525 | ticker := time.NewTicker(time.Minute * 5) 526 | itemTicker := time.NewTicker(time.Second * time.Duration(zabbixConfig.Interval)) 527 | for { 528 | select { 529 | case <-ticker.C: 530 | updateGroupAndHost() 531 | case <-itemTicker.C: 532 | GetItems() 533 | case <-signals: 534 | return 535 | case <-ctx.Done(): 536 | return 537 | } 538 | } 539 | } 540 | 541 | func main() { 542 | configParse() 543 | Init() 544 | er := zApi.Login() 545 | if er != nil { 546 | _, _ = fmt.Fprintf(os.Stderr, "login failed:%s\n", er) 547 | os.Exit(1) 548 | } 549 | Loop(context.Background()) 550 | } 551 | --------------------------------------------------------------------------------