1# 2ping - A bi-directional ping utility 2# Copyright (C) 2010-2021 Ryan Finnie 3# SPDX-License-Identifier: GPL-2.0+ 4 5import gettext 6import platform 7import random as _pyrandom 8 9try: 10 import distro 11except ImportError as e: 12 distro = e 13 14try: 15 from Cryptodome.Cipher import AES 16except ImportError as e: 17 try: 18 from Crypto.Cipher import AES 19 except ImportError: 20 AES = e 21 22 23_ = gettext.translation("2ping", fallback=True).gettext 24_pl = gettext.translation("2ping", fallback=True).ngettext 25 26try: 27 random = _pyrandom.SystemRandom() 28 random_is_systemrandom = True 29except AttributeError: 30 random = _pyrandom 31 random_is_systemrandom = False 32 33 34def twoping_checksum(packet): 35 """Calculate 2ping checksum on a 2ping packet 36 37 Packet may be bytes or bytearray, return is a 32-bit int. 38 Checksum is calculated as described by the 2ping protocol 39 specification. 40 """ 41 checksum = 0 42 43 if (len(packet) % 2) == 1: 44 # Convert from (possible) bytearray to bytes before appending 45 packet = bytes(packet) + b"\x00" 46 47 for i in range(0, len(packet), 2): 48 checksum = checksum + (packet[i] << 8) + packet[i + 1] 49 checksum = (checksum & 0xFFFF) + (checksum >> 16) 50 51 checksum = ~checksum & 0xFFFF 52 53 if checksum == 0: 54 checksum = 0xFFFF 55 56 return checksum 57 58 59def div0(n, d): 60 """Pretend we live in a world where n / 0 == 0""" 61 return 0 if d == 0 else n / d 62 63 64def npack(i, minimum=1): 65 """Pack int to network bytes 66 67 Takes an int and packs it to a network (big-endian) bytes. 68 If minimum is specified, bytes output is padded with zero'd bytes. 69 Minimum does not need to be aligned to 32-bits, 64-bits, etc. 70 """ 71 out = bytearray() 72 while i >= 256: 73 out.insert(0, i & 0xFF) 74 i = i >> 8 75 out.insert(0, i) 76 out_len = len(out) 77 if out_len < minimum: 78 out = bytearray(minimum - out_len) + out 79 return bytes(out) 80 81 82def nunpack(b): 83 """Unpack network bytes to int 84 85 Takes arbitrary length network (big-endian) bytes, and returns an 86 int. 87 """ 88 out = 0 89 for x in b: 90 out = (out << 8) + x 91 return out 92 93 94def platform_info(): 95 """Return a string containing platform/OS information""" 96 platform_name = platform.system() 97 platform_machine = platform.machine() 98 platform_info = "{} {}".format(platform_name, platform_machine) 99 distro_name = "" 100 distro_version = "" 101 distro_info = "" 102 if not isinstance(distro, ImportError): 103 if hasattr(distro, "name"): 104 distro_name = distro.name() 105 if hasattr(distro, "version"): 106 distro_version = distro.version() 107 if distro_name: 108 distro_info = distro_name 109 if distro_version: 110 distro_info += " " + distro_version 111 return "{} ({})".format(platform_info, distro_info) 112 else: 113 return platform_info 114 115 116def fuzz_bytearray(data, percent, start=0, end=None): 117 """Fuzz a bytearray in-place 118 119 Each bit has a <percent> chance of being flipped. 120 """ 121 if end is None: 122 end = len(data) 123 for p in range(start, end): 124 xor_byte = 0 125 for i in range(8): 126 if random.random() < (percent / 100.0): 127 xor_byte = xor_byte + (2 ** i) 128 data[p] = data[p] ^ xor_byte 129 130 131def fuzz_packet(packet, percent): 132 """Fuzz a dumped 2ping packet 133 134 Each bit in the opcode areas has a <percent> chance of being flipped. 135 Each bit in the magic number and checksum have a <percent> / 10 136 chance of being flipped. 137 138 Returns the fuzzed packet. 139 """ 140 packet = bytearray(packet) 141 142 # Fuzz the entire packet 143 fuzz_bytearray(packet, percent, 4) 144 145 # Fuzz the magic number, at a lower probability 146 fuzz_bytearray(packet, percent / 10.0, 0, 2) 147 148 # Recalculate the checksum 149 packet[2:4] = b"\x00\x00" 150 packet[2:4] = npack(twoping_checksum(packet), 2) 151 152 # Fuzz the recalculated checksum itself, at a lower probability 153 fuzz_bytearray(packet, percent / 10.0, 2, 4) 154 155 return bytes(packet) 156 157 158def stats_time(seconds): 159 """Convert seconds to ms/s/m/h/d/y time string""" 160 161 conversion = ( 162 (1000, "ms"), 163 (60, "s"), 164 (60, "m"), 165 (24, "h"), 166 (365, "d"), 167 (None, "y"), 168 ) 169 out = "" 170 rest = int(seconds * 1000) 171 for (div, suffix) in conversion: 172 if div is None: 173 if out: 174 out = " " + out 175 out = "{}{}{}".format(rest, suffix, out) 176 break 177 p = rest % div 178 rest = int(rest / div) 179 if p > 0: 180 if out: 181 out = " " + out 182 out = "{}{}{}".format(p, suffix, out) 183 if rest == 0: 184 break 185 return out 186