├── .gitignore ├── README.md ├── ping_dnscrypt.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | sanstitre* 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dnscrypt-list-ping-sorting 2 | A program to ping and sort the DNS servers proposed by dnscrypt, see here: https://dnscrypt.info/ 3 | 4 | The script pings 5 times each server, and displays the average and the error on the mean and the reliablity. The final display lists all the servers that responded, sorted by their ping time. 5 | 6 | It's written in python3 and (unfortunately) needs root privileges because of the ping function that requires creating sockets — except for the executable version. 7 | 8 | The ping function is from there: https://gist.github.com/pyos/10980172 9 | 10 | # How to use it? 11 | The most straightforward way is simply to go with: 12 | 13 | `wget https://raw.githubusercontent.com/Magalame/Dnscrypt-list-ping-sorting/master/ping_dnscrypt.py` 14 | 15 | then 16 | 17 | `sudo python3 ping_dnscrypt.py` 18 | 19 | **If you think it goes too slowly** you can increase the speed by using threading (although it might make the data less reliable): 20 | 21 | `sudo python3 ping_dnscrypt.py -t` 22 | 23 | The default number of ping per server is 5 without threading, 10 with. You can specify the number if you want to change that (for example if you want to increase the precision of the 'reliability' parameter): 24 | 25 | `sudo python3 ping_dnscrypt.py -n yournumberhere` 26 | 27 | Although if you increase this number too much with the threading activated you might overload your network, and you will end up with inaccurate results. To avoid that you can define delay between every single ping request, the default is 0.02 seconds: 28 | 29 | `sudo python3 ping_dnscrypt.py -p yourdelayinsecondshere` 30 | 31 | You can also define a delay between each time we start pinging a server (as the program goes through a list), the default is 0.2 seconds: 32 | 33 | `sudo python3 ping_dnscrypt.py -s yourdelayinsecondshere` 34 | 35 | 36 | 37 | # You don't have python or the good version of python, and you want to download an executable (Windows)? 38 | Portable version: https://drive.google.com/file/d/1o3ndcbgBnt4d_ErJ64FXeKWJMU3cgvK-/view?usp=sharing 39 | 40 | And for 32 bits: https://drive.google.com/open?id=1aDX-0h7k9nAIBg0lFi5961_kjKOa7vPw 41 | 42 | Once downloaded, find where is located 'pingdnscrypt.exe', then run open a terminal and type 43 | 44 | `ping_dnscrypt.exe` 45 | 46 | # It might happen that the display isn't great, some charac returns are missed sometimes 47 | I don't know how to solve it yet, for now I strongly recommend you use it in full screen (not the F11 type of fullscren, just when you click on the square next to the cross at the top right corner of the terminal). 48 | -------------------------------------------------------------------------------- /ping_dnscrypt.py: -------------------------------------------------------------------------------- 1 | import os 2 | import csv 3 | import time 4 | import random 5 | import struct 6 | import select 7 | import socket 8 | import sys 9 | import urllib.request 10 | from threading import Lock,Thread 11 | import argparse 12 | from timeit import default_timer as timer 13 | #-----------------------------------general variables 14 | if any('SPYDER' in name for name in os.environ): 15 | executed_in_spyder = True 16 | else: 17 | executed_in_spyder = False 18 | 19 | 20 | 21 | #-----------------------------------parsing of arguments 22 | parser = argparse.ArgumentParser() 23 | 24 | parser.add_argument("-n", "--number_ping", type=int, help="sets the number of times a server is pinged") 25 | parser.add_argument("-p", "--ping_delay", type=float, help="sets delay between each ping") 26 | parser.add_argument("-s", "--server_delay", type=float, help="sets delay between pinging of each server") 27 | parser.add_argument("-v", "--verbose", help="increase output verbosity",action="store_true") 28 | parser.add_argument("-t", "--threading", help="enable threading, desables ping and server delay",action="store_true") 29 | parser.add_argument("-m", "--time_out", type=float, help="sets the timeout for pinging") 30 | 31 | 32 | args = parser.parse_args() 33 | 34 | if not args.ping_delay: 35 | if args.threading: 36 | args.ping_delay = 0.02 37 | else: 38 | args.ping_delay = 0 39 | 40 | if not args.server_delay: 41 | if args.threading: 42 | args.server_delay = 0.3 43 | else: 44 | args.server_delay = 0 45 | 46 | if not args.number_ping: 47 | if args.threading: 48 | args.number_ping = 10 49 | else: 50 | args.number_ping = 5 51 | 52 | if not args.time_out: 53 | args.time_out = 0.5 54 | 55 | if not args.threading: 56 | print("-----------------------------------------------") 57 | print("This program will ping through a list of ips, so it will take quite some time.\nTo increase the speed you can restart the program with the \'-t\' option.") 58 | print("-----------------------------------------------") 59 | 60 | 61 | #-------------------------------some useful functions for threading 62 | 63 | start_time = time.time() 64 | verrou = Lock() 65 | 66 | def printt(msg): 67 | with verrou: 68 | sys.stdout.write(str(msg)+"\n") 69 | 70 | def executer(task): 71 | with verrou: 72 | task 73 | 74 | #--------------------------------------- 75 | 76 | class PingThread (Thread): 77 | def __init__(self, ip, port, ipv6): 78 | Thread.__init__(self) 79 | self.ip = ip 80 | self.port = port 81 | self.ipv6 = ipv6 82 | 83 | def run(self): 84 | 85 | global ipv6_available 86 | 87 | self.ping_result = None #we set it to none in case the ping function fails, it spares a bit of error handling 88 | self.port_originally_none = False 89 | 90 | if self.port == None: #from the original function 91 | self.port = 53 92 | self.port_originally_none = True 93 | 94 | if not self.ipv6: #ipv4 95 | try: 96 | self.ping_result = pingtcp(self.ip,int(self.port)) 97 | if self.ping_result == None: 98 | 99 | if self.port_originally_none: 100 | self.ping_result = pingtcp(self.ip,443) #if port 53 didn't work try port 443 101 | 102 | if self.ping_result == None: 103 | self.ping_result = ping(self.ip) 104 | 105 | else: 106 | self.ping_result = ping(self.ip) 107 | 108 | except: 109 | raise 110 | 111 | else: # no need to re-check if ipv6 is available, it cannot be called anyway if ipv6_available is false 112 | try: #will need to implement a ipv6 implementation of the tcp ping 113 | self.ping_result = ping(self.ip,ipv6=self.ipv6) 114 | 115 | except: 116 | raise 117 | 118 | 119 | class meanPingThread (Thread): 120 | def __init__(self, ip, row, nb_ping, port, ipv6=False): 121 | Thread.__init__(self) 122 | self.ip = ip 123 | self.row = row 124 | self.nb_ping = nb_ping 125 | self.port = port 126 | self.ipv6 = ipv6 127 | #self.terminal = sys.stdout 128 | 129 | def run(self): 130 | global liste 131 | global down_liste 132 | 133 | 134 | try: 135 | 136 | if self.ipv6: #ipv6 137 | ping_result = meanPing(self.ip,args.number_ping,int(self.port), True) 138 | else: 139 | ping_result = meanPing(self.ip[0].split(':')[0],args.number_ping,self.port) #le deuxieme parametre est le nombre de ping 140 | 141 | except Exception as e: 142 | 143 | printt(str(e)) 144 | 145 | ping_result = [None] 146 | 147 | if ping_result[0] != None: 148 | 149 | self.row.append(ping_result[0]) 150 | self.row.append(ping_result[1]) 151 | self.row.append(ping_result[2]) 152 | liste.append(self.row) 153 | 154 | else: 155 | down_liste.append(self.row) 156 | 157 | #-------------------------------------some statistical functions 158 | 159 | def mean(list): 160 | try: 161 | return sum(list) / len(list) 162 | except ZeroDivisionError: 163 | return None 164 | 165 | def std(list): 166 | mean_var = mean(list) 167 | variance = sum([(x-mean_var)**2 for x in list]) / len(list) 168 | return variance**0.5 169 | 170 | #---------------------------------base ping function 171 | 172 | def chk(data): 173 | x = sum(x << 8 if i % 2 else x for i, x in enumerate(data)) & 0xFFFFFFFF 174 | x = (x >> 16) + (x & 0xFFFF) 175 | x = (x >> 16) + (x & 0xFFFF) 176 | return struct.pack('1: 272 | half = len(list)//2 273 | left = list[:half] 274 | right = list[half:] 275 | 276 | mergeSort(left) 277 | mergeSort(right) 278 | 279 | merge(list,left,right) 280 | 281 | #------------------------------- 282 | 283 | #pinglist = [] 284 | #down = 0 285 | std_liste = [] #for debug purposes 286 | #time_to_sleep = 0.1 287 | def meanPing(addr,nb,port,ipv6=False): 288 | # global pinglist 289 | global std_liste 290 | pinglist = [] 291 | 292 | # global down 293 | down = 0 294 | 295 | threadlist = [] 296 | 297 | for i in range(0,nb): 298 | 299 | time.sleep(args.ping_delay) #we add this little delay otherwise we might make the network saturate and give us wrong data 300 | threadlist.append(PingThread(addr,port,ipv6)) 301 | try: 302 | threadlist[i].start() 303 | except RuntimeError: 304 | printt("The device cannot handle the number of threads. Please decrease the ping rate, or increase the delay, or disable pinging") 305 | os._exit(0) 306 | #with verrou: 307 | #sys.stdout.write("[+] ip: " + addr +" thread:" + hex(id(threadlist[i]))+"\n") 308 | 309 | for thread in threadlist: 310 | thread.join() 311 | #with verrou: 312 | # sys.stdout.write("[-] ip: " + addr +" thread:" + hex(id(thread))+"\n") 313 | 314 | if thread.ping_result != None: 315 | pinglist.append(thread.ping_result) 316 | else: 317 | # print(down) 318 | down = down + 1 319 | 320 | pct_error = down/float(nb) 321 | 322 | if pct_error != 1: 323 | std_liste.append(std(pinglist)/(nb**0.5)) 324 | return [mean(pinglist),std(pinglist)/(nb**0.5),down/nb] 325 | else: 326 | return [None,None,1.0] 327 | 328 | ipv6_available = True #as not everyone has ipv6, we want to keep that somewhere 329 | liste = [] #list of all servers that answered 330 | down_liste = [] #list of all servers that do not answer 331 | def pingDnscryptFile(filename): 332 | global ipv6_available 333 | 334 | global liste 335 | liste = [] 336 | 337 | global down_liste 338 | down_liste = [] 339 | 340 | count_row = 0 341 | count_thread = 0 #pour suivre le nombre de thread vraiment commences qui est different du nombre de row 342 | 343 | threadlist = [] #avoir la liste des threads pour les gerer 344 | 345 | reader = csv.reader(open(filename, "rt"), delimiter=",") 346 | # ipv6_available = True 347 | print("-----------------------Pinging servers-----------------------") 348 | for row in reader: 349 | if row[0] != "Name": 350 | count_row = count_row + 1 351 | 352 | ip = row[10].split("[") 353 | 354 | if ip[0] != '': #if ipv4, because it is formatted a certain way in the csv file 355 | 356 | split_port = row[10].split(":") 357 | 358 | if len(split_port) == 2: 359 | port = split_port[1] 360 | else: 361 | port = None 362 | 363 | threadlist.append(meanPingThread(ip,row,args.number_ping, port)) 364 | count_thread = count_thread+1 365 | try: 366 | threadlist[count_thread-1].start() 367 | except RuntimeError: 368 | printt("The device cannot handle the number of threads. Please decrease the ping rate, or increase the delay, or disable pinging") 369 | os._exit(0) 370 | 371 | time.sleep(args.server_delay) 372 | 373 | if not args.threading: #if not threading then we join each thread before going on 374 | threadlist[count_thread-1].join() 375 | 376 | #print(threadlist[count_thread].ip) 377 | 378 | elif ipv6_available: #if ipv6 and if ipv6 available 379 | #print(ip[1]) 380 | port = ip[1].split("]")[1].split(':')[1] 381 | 382 | threadlist.append(meanPingThread(ip[1].split("]")[0],row,args.number_ping, port, True)) 383 | count_thread = count_thread+1 384 | try: 385 | threadlist[count_thread-1].start() 386 | except RuntimeError: 387 | printt("The device cannot handle the number of threads. Please decrease the ping rate, or increase the delay, or disable pinging") 388 | os._exit(0) 389 | time.sleep(args.server_delay) 390 | 391 | if not args.threading: #if not threading then we join each thread before going on 392 | threadlist[count_thread-1].join() 393 | 394 | else: 395 | #print("Ip not treated:",row[10]) 396 | pass 397 | if not executed_in_spyder: 398 | sys.stdout.write("Number of servers pinged: %d \r" % (count_row) ) 399 | else: 400 | sys.stdout.write("Number of servers pinged: %d \n" % (count_row) ) 401 | 402 | printt("----------------------Loading the result---------------------") 403 | count_joined_thread = 0 404 | 405 | if args.threading: 406 | 407 | for thread in threadlist: 408 | if not executed_in_spyder: 409 | sys.stdout.write("Number of thread joined: %d \r" % (count_joined_thread)) 410 | else: 411 | sys.stdout.write("Number of thread joined: %d \n" % (count_joined_thread)) 412 | thread.join() 413 | count_joined_thread = count_joined_thread + 1 414 | 415 | 416 | print("---------------------Sorting the list-----------------------") 417 | # print(len(threadlist)) 418 | mergeSort(liste) 419 | 420 | print("------------------------Merged list-------------------------") 421 | 422 | if ipv6_available: #as the length of the ip address is different and wil affect layout 423 | print("Name \t\t\tIP address \t\t\t\tping \t\t reliability \tDNSSEC \tNo log \tLocation") 424 | else: 425 | print("Name \t\t\tIP address \t\tping \t\t reliability \tDNSSEC \tNo log \tLocation") 426 | 427 | for row in liste: 428 | 429 | if len(row[0])<8:a=3 #some computation so organize output 430 | elif len(row[0])<16:a=2 431 | else:a=1 432 | 433 | if ipv6_available: 434 | 435 | if len(row[10])<16:b=4 #same here, different computation because again different ip length with ipv6 436 | elif len(row[10])<24:b=3 437 | elif len(row[10])<32:b=2 438 | else:b=1 439 | else: 440 | if len(row[10])<16:b=2 441 | else:b=1 442 | 443 | print(row[0] + a*"\t" + row[10] + b*"\t" + str(round(row[14]*1000,2)) + "\t" + "+/-" + str(round(row[15]*1000,2)) + "ms" + "\t" + str(round(100-row[16]*100,1)) + "%" + "\t" + row[7] + "\t" + row[8] + "\t" + row[3]) 444 | 445 | if args.verbose: 446 | print("Moyenne std:",round(mean(std_liste)*1000,3)) 447 | print("Delais between each ping:",args.ping_delay,"\nDelais between each server:",args.server_delay,"\nNb ping:",args.number_ping) 448 | 449 | 450 | def pingDnscrypt(): 451 | 452 | if not os.path.isfile("dnscrypt-resolvers.csv"): 453 | urllib.request.urlretrieve("https://raw.githubusercontent.com/dyne/dnscrypt-proxy/master/dnscrypt-resolvers.csv","dnscrypt-resolvers.csv") 454 | pingDnscryptFile("dnscrypt-resolvers.csv") 455 | 456 | 457 | if __name__ == '__main__': 458 | pingDnscrypt() 459 | if args.verbose: 460 | print("--- Executed in %s seconds ---" % (time.time() - start_time)) 461 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from cx_Freeze import setup, Executable 3 | 4 | # Dependencies are automatically detected, but it might need fine tuning. 5 | #build_exe_options = {"packages": ["os"], "excludes": ["tkinter"]} 6 | 7 | # GUI applications require a different base on Windows (the default is for a 8 | # console application). 9 | base = None 10 | if sys.platform == "win32": 11 | base = "console" 12 | 13 | setup( name = "Ping dnscrypt", 14 | version = "0.1", 15 | description = "Pings through dnscrypt list of servers", 16 | #options = {"build_exe": build_exe_options}, 17 | executables = [Executable("pingdnscrypt.py", base=base)]) --------------------------------------------------------------------------------