From 1f2a4b0fb5b6cc6eb2f79a81859cd8ae86fad45f Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Tue, 29 Aug 2023 17:02:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20telnet=E3=80=81ping=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=89=B9=E9=87=8F=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/settings/tools/ping.py | 59 +++++++++++++++++-------------- apps/settings/tools/telnet.py | 66 ++++++++++++++++------------------- apps/settings/utils/common.py | 12 ++++--- apps/settings/ws.py | 10 +++--- 4 files changed, 76 insertions(+), 71 deletions(-) diff --git a/apps/settings/tools/ping.py b/apps/settings/tools/ping.py index 556bb9e5b..8a40110e8 100644 --- a/apps/settings/tools/ping.py +++ b/apps/settings/tools/ping.py @@ -8,6 +8,7 @@ import struct import time from common.utils import lookup_domain +from settings.utils import generate_ips # From /usr/include/linux/icmp.h; your milage may vary. ICMP_ECHO_REQUEST = 8 # Seems to be the same on Solaris. @@ -128,7 +129,7 @@ def ping(dest_addr, timeout, psize, flag=0): return delay -async def verbose_ping(dest_ip, timeout=2, count=5, psize=64, display=None): +async def verbose_ping(dest_ips, timeout=2, count=5, psize=64, display=None): """ Send `count' ping with `psize' size to `dest_addr' with the given `timeout' and display the result. @@ -136,34 +137,38 @@ async def verbose_ping(dest_ip, timeout=2, count=5, psize=64, display=None): if not display: return - ip, err = lookup_domain(dest_ip) - if not ip: - await display(err) - return + result = {} + ips = generate_ips(dest_ips) + await display(f'Total valid address: {len(ips)}\r\n') + for dest_ip in ips: + await display(f'PING {dest_ip}: 56 data bytes') + # 切换异步协程 + await asyncio.sleep(0.1) + error_count = 0 + for i in range(count): + try: + delay = ping(dest_ip, timeout, psize) + except socket.gaierror as e: + await display("Failed (socket error: '%s')" % str(e)) + error_count += 1 + break - await display("PING %s (%s): 56 data bytes" % (dest_ip, ip)) - await asyncio.sleep(0.1) - error_count = 0 - for i in range(count): - try: - delay = ping(dest_ip, timeout, psize) - except socket.gaierror as e: - await display("Failed (socket error: '%s')" % str(e)) - error_count += 1 - break + if delay is None: + await display("Request timeout for icmp_seq %i" % i) + error_count += 1 + else: + delay *= 1000 + await display("64 bytes from %s: time=%.3f ms" % (dest_ip, delay)) + await asyncio.sleep(1) + # 只要有包通过,就认为address是通的 + result[dest_ip] = 'failed' if error_count == count else 'ok' + await display(f'{count} packets transmitted, ' + f'{count - error_count} packets received, ' + f'{(error_count / count) * 100}% packet loss\r\n') - if delay is None: - await display("Request timeout for icmp_seq %i" % i) - error_count += 1 - else: - delay *= 1000 - await display("64 bytes from %s: icmp_seq=0 ttl=115 time=%.3f ms" % (ip, delay)) - await asyncio.sleep(1) - - await display(f'--- {dest_ip} ping statistics ---') - await display(f'{count} packets transmitted, ' - f'{count - error_count} packets received, ' - f'{(error_count / count) * 100}% packet loss') + await display(f'----- Ping statistics -----') + for k, v in result.items(): + await display(f'{k}: {v}') if __name__ == "__main__": diff --git a/apps/settings/tools/telnet.py b/apps/settings/tools/telnet.py index b54a393d8..796118383 100644 --- a/apps/settings/tools/telnet.py +++ b/apps/settings/tools/telnet.py @@ -1,57 +1,53 @@ # -*- coding: utf-8 -*- # import asyncio +import socket +import telnetlib -from common.utils import lookup_domain +from settings.utils import generate_ips PROMPT_REGEX = r'[\<|\[](.*)[\>|\]]' async def telnet(dest_addr, port_number=23, timeout=10): + loop = asyncio.get_running_loop() try: - reader, writer = await asyncio.wait_for( - asyncio.open_connection(dest_addr, port_number), timeout - ) + connection = await loop.run_in_executor(None, telnetlib.Telnet, dest_addr, port_number, timeout) except asyncio.TimeoutError: return False, 'Timeout' - except (ConnectionRefusedError, OSError) as e: + except (ConnectionRefusedError, socket.timeout, socket.gaierror) as e: return False, str(e) - try: - # 发送命令 - writer.write(b"command\r\n") - await writer.drain() - # 读取响应 - response = await reader.readuntil() - except asyncio.TimeoutError: - writer.close() - await writer.wait_closed() - return False, 'Timeout' - writer.close() - await writer.wait_closed() - return True, response.decode('utf-8', 'ignore') + expected_regexes = [bytes(PROMPT_REGEX, encoding='ascii')] + __, __, output = connection.expect(expected_regexes, timeout=3) + return True, output.decode('utf-8', 'ignore') -async def verbose_telnet(dest_ip, dest_port=23, timeout=10, display=None): +async def verbose_telnet(dest_ips, dest_port=23, timeout=10, display=None): if not display: return - ip, err = lookup_domain(dest_ip) - if not ip: - await display(err) - return + result = {} + ips = generate_ips(dest_ips) + await display(f'Total valid address: {len(ips)}\r\n') + for dest_ip in ips: + await display(f'Trying ({dest_ip}:{dest_port})') + try: + is_connective, resp = await telnet(dest_ip, dest_port, timeout) + if is_connective: + result[dest_ip] = 'ok' + msg = f'Connected to {dest_ip} {dest_port} {resp}.\r\n' \ + f'Connection closed by foreign host.' + else: + result[dest_ip] = 'failed' + msg = f'Unable to connect to remote host\r\n' \ + f'Reason: {resp}' + except Exception as e: + msg = 'Error: %s' % e + await display(f'{msg}\r\n') - await display(f'Trying {dest_ip} ({ip}:{dest_port})') - try: - is_connective, resp = await telnet(dest_ip, dest_port, timeout) - if is_connective: - msg = f'Connected to {dest_ip} {dest_port} {resp}.\r\n' \ - f'Connection closed by foreign host.' - else: - msg = f'Unable to connect to remote host\r\n' \ - f'Reason: {resp}' - except Exception as e: - msg = 'Error: %s' % e - await display(msg) + await display(f'----- Telnet statistics -----') + for k, v in result.items(): + await display(f'{k}: {v}') if __name__ == "__main__": diff --git a/apps/settings/utils/common.py b/apps/settings/utils/common.py index e549f9b8c..1e1f94f12 100644 --- a/apps/settings/utils/common.py +++ b/apps/settings/utils/common.py @@ -3,6 +3,8 @@ from jumpserver.context_processor import default_interface from django.conf import settings from IPy import IP +from common.utils import lookup_domain + def get_interface_setting_or_default(): if not settings.XPACK_ENABLED: @@ -15,21 +17,23 @@ def get_login_title(): return get_interface_setting_or_default()['login_title'] -def generate_ips(ip_string): +def generate_ips(address_string): # 支持的格式 # 192.168.1.1,192.168.1.2 # 192.168.1.1-12 | 192.168.1.1-192.168.1.12 | 192.168.1.0/30 | 192.168.1.1 ips = [] - ip_list = ip_string.split(',') + ip_list = address_string.split(',') if len(ip_list) > 1: for ip in ip_list: try: ips.append(str(IP(ip))) except ValueError: - pass + ip, err = lookup_domain(ip) + if not err: + ips.append(ip) return ips - ip_list = ip_string.split('-') + ip_list = address_string.split('-') try: if len(ip_list) == 2: start_ip, end_ip = ip_list diff --git a/apps/settings/ws.py b/apps/settings/ws.py index b15d61ea7..5fec6ebb4 100644 --- a/apps/settings/ws.py +++ b/apps/settings/ws.py @@ -22,19 +22,19 @@ class ToolsWebsocket(AsyncJsonWebsocketConsumer): await self.close() async def send_msg(self, msg=''): - await self.send_json({'msg': msg + '\r\n'}) + await self.send_json({'msg': f'{msg}\r\n'}) - async def imitate_ping(self, dest_ip, timeout=3, count=5, psize=64): + async def imitate_ping(self, dest_ips, timeout=3, count=5, psize=64): params = { - 'dest_ip': dest_ip, 'timeout': timeout, + 'dest_ips': dest_ips, 'timeout': timeout, 'count': count, 'psize': psize } logger.info(f'Receive request ping: {params}') await verbose_ping(display=self.send_msg, **params) - async def imitate_telnet(self, dest_ip, dest_port=23, timeout=10): + async def imitate_telnet(self, dest_ips, dest_port=23, timeout=10): params = { - 'dest_ip': dest_ip, 'dest_port': dest_port, 'timeout': timeout, + 'dest_ips': dest_ips, 'dest_port': dest_port, 'timeout': timeout, } logger.info(f'Receive request telnet: {params}') await verbose_telnet(display=self.send_msg, **params)