1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
|
#
# Update bootstrap nodes
#
# This script is used to generate src/tox_bootstrap.h by adding a list
# of bootstrap nodes from https://nodes.tox.chat/
#
# It should be executed on a regular basis (before a release) to make sure
# the list is up to date and contains active bootstrap nodes.
# This will make sure clients can connect to the network quickly and do not have to waste
# time trying to connect to nodes that do no longer exist.
#
# You can run the script like this:
#
# python3 tools/update-bootstrap.py > src/tox_bootstrap.h
#
# Status information will be printed to stderr.
#
import http.client
import json
from datetime import datetime
import re
import sys
# print for stderr
def eprint(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)
# check whether arg is an IP or a hostname
def is_ip(ip):
# these are not exactly nice patterns but the are sufficient to distinguish IP from hostname
ipv4 = re.compile("^\\d+\\.\\d+\\.\\d+\\.\\d+$")
ipv6 = re.compile("^[0-9a-f:]+$")
return ipv4.match(ip) or ipv6.match(ip)
# select a tcp port from a given range, use 443 if available
def select_tcp_port(ports):
if ports.count(443) > 0:
return 443
else:
return ports[0]
# http://stackoverflow.com/questions/18854620/whats-the-best-way-to-split-a-string-into-fixed-length-chunks-and-work-with-the#18854817
def chunkstring(string, length):
return (string[0+i:length+i] for i in range(0, len(string), length))
# get latest node data
connection = http.client.HTTPSConnection('nodes.tox.chat')
connection.request('GET', '/json')
response = connection.getresponse()
nodeData = response.read().decode()
data = json.loads(nodeData)
# print some info
eprint("Last scan: " + datetime.fromtimestamp(data.get('last_scan')).isoformat(' '))
eprint("Last refresh: " + datetime.fromtimestamp(data.get('last_refresh')).isoformat(' '))
eprint("Nodes: " + len(data.get('nodes')).__str__())
# filter out offline nodes
# only keep nodes that are active on tcp and udp
# also filter nodes that specify a hostname instead of IP
# we do not want utox to make DNS queries
# some of those are on a DynDNS so it does not even make sense to do the query in this script
nodes = []
for n in data.get('nodes'):
if not n.get('status_udp') or not n.get('status_tcp'):
continue
if is_ip(n.get('ipv4')):
# eprint(n.get('ipv4'), " udp: " + n.get('port').__str__(), " tcp:", *n.get('tcp_ports'))
# eprint(" pkey:", n.get('public_key'))
nodes.append({
'ip': n.get('ipv4'),
'ipv6': 'false',
'udp': n.get('port'),
'tcp': select_tcp_port(n.get('tcp_ports')),
'version': n.get('version'),
'pubkey': n.get('public_key'),
'maintainer': n.get('maintainer'),
'location': n.get('location'),
})
if is_ip(n.get('ipv6')):
# eprint(n.get('ipv6'), " udp: " + n.get('port').__str__(), " tcp:", *n.get('tcp_ports'))
# eprint(" pkey:", n.get('public_key'))
nodes.append({
'ip': n.get('ipv6'),
'ipv6': 'true',
'udp': n.get('port'),
'tcp': select_tcp_port(n.get('tcp_ports')),
'version': n.get('version'),
'pubkey': n.get('public_key'),
'maintainer': n.get('maintainer'),
'location': n.get('location'),
})
eprint("filtered offline and hostname-only nodes: ", len(nodes), "candidate entries")
# sort by the following criteria:
# - 1. sort by version, prefer nodes that are up to date
# - 2. prefer low udp port, i.e. 443 gets listed higher
# - 3. prefer tcp with port 443 over tcp without it
eprint("sorting by criteria...")
nodes = sorted(nodes, key = lambda n: (n.get('version'), -n.get('udp'), 1 if n.get('tcp') == 443 else 0), reverse=True)
#f = open("tox_bootstrap.h.test", 'w')
f = sys.stdout
f.write("""#ifndef TOX_BOOTSTRAP_H
#define TOX_BOOTSTRAP_H
//
// IMPORTANT: This file is generated by the /tools/update-bootstrap.py script, do not edit manually.
//
struct bootstrap_node {
char *address;
bool ipv6;
uint16_t port_udp;
uint16_t port_tcp;
uint8_t key[32];
} bootstrap_nodes[] = {
""")
# use the first 32 nodes that match the criteria above
k = 0
for n in nodes:
if k >= 32:
break
eprint("adding ", n.get('ip'), " (", n.get('udp'), n.get('tcp'), ") by", n.get('maintainer') + ', ' + n.get('location'), "version: ", n.get('version'))
f.write(" /* by " + n.get('maintainer') + ', ' + n.get('location') + " */\n")
f.write(' { "' + n.get('ip') + '", ' + n.get('ipv6') + ', ' + n.get('udp').__str__() + ', ' + n.get('tcp').__str__() + ",\n")
f.write(" {")
i = 0
for p in chunkstring(n.get('pubkey'), 2):
i += 1
if i > 16:
f.write("\n ")
i = 0
f.write(" 0x" + p + ",")
f.write(" }\n },\n")
k += 1
f.write("""
};
#endif
""")
f.close()
eprint("added", k, "nodes.")
|