File: update-bootstrap.py

package info (click to toggle)
utox 0.18.1-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye, sid
  • size: 10,792 kB
  • sloc: ansic: 102,804; objc: 2,385; cpp: 2,069; java: 1,094; python: 868; javascript: 703; xml: 424; sh: 208; makefile: 53; sed: 9
file content (154 lines) | stat: -rw-r--r-- 5,008 bytes parent folder | download
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.")