Mise à jour effectuée, merci de nous signaler tout dysfonctionnement ! | Upgrade done, please let us know about any dysfunction!

network_tools.py 6.81 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
"""
Copyright  2016-2019 Maël Azimi <m.a@moul.re>

Silkaj is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Silkaj is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with Silkaj. If not, see <https://www.gnu.org/licenses/>.
"""

Moul's avatar
Moul committed
18
from __future__ import unicode_literals
19
20
from ipaddress import ip_address
from json import loads
Moul's avatar
Moul committed
21
22
import socket
import urllib.request
23
import logging
24
from sys import exit, stderr
25
from commandlines import Command
Moul's avatar
Moul committed
26

Moul's avatar
Moul committed
27
28
29
30
31
32
from silkaj.constants import (
    G1_DEFAULT_ENDPOINT,
    G1_TEST_DEFAULT_ENDPOINT,
    CONNECTION_TIMEOUT,
)

Moul's avatar
Moul committed
33

34
def discover_peers(discover):
35
36
37
38
39
40
    """
    From first node, discover his known nodes.
    Remove from know nodes if nodes are down.
    If discover option: scan all network to know all nodes.
        display percentage discovering.
    """
41
    endpoints = parse_endpoints(get_request("network/peers")["peers"])
Moul's avatar
Moul committed
42
43
    if discover:
        print("Discovering network")
44
    for i, endpoint in enumerate(endpoints):
Moul's avatar
Moul committed
45
46
        if discover:
            print("{0:.0f}%".format(i / len(endpoints) * 100))
47
48
        if best_node(endpoint, False) is None:
            endpoints.remove(endpoint)
Moul's avatar
Moul committed
49
        elif discover:
50
            endpoints = recursive_discovering(endpoints, endpoint)
51
    return endpoints
52

Moul's avatar
Moul committed
53

54
def recursive_discovering(endpoints):
55
56
57
58
    """
    Discover recursively new nodes.
    If new node found add it and try to found new node from his known nodes.
    """
59
    news = parse_endpoints(get_request("network/peers")["peers"])
60
    for new in news:
Moul's avatar
Moul committed
61
        if best_node(new, False) is not None and new not in endpoints:
62
63
            endpoints.append(new)
            recursive_discovering(endpoints, new)
64
    return endpoints
Moul's avatar
Moul committed
65

Moul's avatar
Moul committed
66
67
68
69
70
71
72
73

def parse_endpoints(rep):
    """
    endpoints[]{"domain", "ip4", "ip6", "pubkey"}
    list of dictionaries
    reps: raw endpoints
    """
    i, j, endpoints = 0, 0, []
Moul's avatar
Moul committed
74
75
    while i < len(rep):
        if rep[i]["status"] == "UP":
Moul's avatar
Moul committed
76
77
            while j < len(rep[i]["endpoints"]):
                ep = parse_endpoint(rep[i]["endpoints"][j])
Moul's avatar
Moul committed
78
                j += 1
79
80
81
                if ep is None:
                    break
                ep["pubkey"] = rep[i]["pubkey"]
Moul's avatar
Moul committed
82
                endpoints.append(ep)
Moul's avatar
Moul committed
83
84
        i += 1
        j = 0
85
    return endpoints
Moul's avatar
Moul committed
86

Moul's avatar
Moul committed
87

88
89
90
91
92
93
94
95
96
97
98
99
class EndPoint(object):
    __instance = None

    # Try to inheritate this part for all singleton classes
    def __new__(cls):
        if EndPoint.__instance is None:
            EndPoint.__instance = object.__new__(cls)
        return EndPoint.__instance

    def __init__(self):
        cli_args = Command()
        ep = dict()
Moul's avatar
Moul committed
100
        if cli_args.contains_switches("p"):
101
102
103
104
105
            peer = cli_args.get_definition("p")
            if ":" in peer:
                ep["domain"], ep["port"] = peer.rsplit(":", 1)
            else:
                ep["domain"], ep["port"] = peer, "443"
Moul's avatar
Moul committed
106
        else:
Moul's avatar
Moul committed
107
108
109
110
111
112
            ep["domain"], ep["port"] = (
                G1_TEST_DEFAULT_ENDPOINT
                if cli_args.contains_switches("gtest")
                else G1_DEFAULT_ENDPOINT
            )
        if ep["domain"].startswith("[") and ep["domain"].endswith("]"):
113
            ep["domain"] = ep["domain"][1:-1]
114
115
116
        self.ep = ep


Moul's avatar
Moul committed
117
118
119
120
121
122
123
def parse_endpoint(rep):
    """
    rep: raw endpoint, sep: split endpoint
    domain, ip4 or ip6 could miss on raw endpoint
    """
    ep, sep = {}, rep.split(" ")
    if sep[0] == "BASIC_MERKLED_API":
Moul's avatar
Moul committed
124
125
        if check_port(sep[-1]):
            ep["port"] = sep[-1]
Moul's avatar
Moul committed
126
127
128
129
130
131
        if (
            len(sep) == 5
            and check_ip(sep[1]) == 0
            and check_ip(sep[2]) == 4
            and check_ip(sep[3]) == 6
        ):
Moul's avatar
Moul committed
132
133
134
135
136
137
            ep["domain"], ep["ip4"], ep["ip6"] = sep[1], sep[2], sep[3]
        if len(sep) == 4:
            ep = endpoint_type(sep[1], ep)
            ep = endpoint_type(sep[2], ep)
        if len(sep) == 3:
            ep = endpoint_type(sep[1], ep)
138
        return ep
139
    else:
140
        return None
Moul's avatar
Moul committed
141

Moul's avatar
Moul committed
142

Moul's avatar
Moul committed
143
144
def endpoint_type(sep, ep):
    typ = check_ip(sep)
Moul's avatar
Moul committed
145
146
147
148
149
150
    if typ == 0:
        ep["domain"] = sep
    elif typ == 4:
        ep["ip4"] = sep
    elif typ == 6:
        ep["ip6"] = sep
151
    return ep
Moul's avatar
Moul committed
152

Moul's avatar
Moul committed
153

Moul's avatar
Moul committed
154
def check_ip(address):
Moul's avatar
Moul committed
155
    try:
156
        return ip_address(address).version
Moul's avatar
Moul committed
157
158
159
    except:
        return 0

Moul's avatar
Moul committed
160

161
def get_request(path, ep=EndPoint().ep):
Moul's avatar
Moul committed
162
    address = best_node(ep, False)
Moul's avatar
Moul committed
163
164
    if address is None:
        return address
165
    url = "http://" + ep[address] + ":" + ep["port"] + "/" + path
166
167
    if ep["port"] == "443":
        url = "https://" + ep[address] + "/" + path
Moul's avatar
Moul committed
168
    request = urllib.request.Request(url)
169
    response = urllib.request.urlopen(request, timeout=CONNECTION_TIMEOUT)
Moul's avatar
Moul committed
170
    encoding = response.info().get_content_charset("utf8")
171
    return loads(response.read().decode(encoding))
Moul's avatar
Moul committed
172

Moul's avatar
Moul committed
173

174
def post_request(path, postdata, ep=EndPoint().ep):
Moul's avatar
Moul committed
175
    address = best_node(ep, False)
Moul's avatar
Moul committed
176
177
    if address is None:
        return address
Tortue95's avatar
Tortue95 committed
178
179
180
    url = "http://" + ep[address] + ":" + ep["port"] + "/" + path
    if ep["port"] == "443":
        url = "https://" + ep[address] + "/" + path
Moul's avatar
Moul committed
181
    request = urllib.request.Request(url, bytes(postdata, "utf-8"))
Tortue95's avatar
Tortue95 committed
182
    try:
183
        response = urllib.request.urlopen(request, timeout=CONNECTION_TIMEOUT)
Tortue95's avatar
Tortue95 committed
184
    except urllib.error.URLError as e:
185
186
        print(e, file=stderr)
        exit(1)
Moul's avatar
Moul committed
187
    encoding = response.info().get_content_charset("utf8")
188
    return loads(response.read().decode(encoding))
Tortue95's avatar
Tortue95 committed
189

Moul's avatar
Moul committed
190

Moul's avatar
Moul committed
191
192
def best_node(ep, main):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
193
    s.settimeout(CONNECTION_TIMEOUT)
194
    addresses, port = ("domain", "ip6", "ip4"), int(ep["port"])
Moul's avatar
Moul committed
195
196
197
198
    for address in addresses:
        if address in ep:
            try:
                s.connect((ep[address], port))
199
                s.close()
200
                return address
201
            except Exception as e:
Moul's avatar
Moul committed
202
203
204
                logging.debug(
                    "Connection to endpoint %s (%s) failled (%s)" % (ep, address, e)
                )
Moul's avatar
Moul committed
205
    if main:
vincentux's avatar
vincentux committed
206
        print("Wrong node given as argument", file=stderr)
207
        exit(1)
208
    return None
Moul's avatar
Moul committed
209
210


Moul's avatar
Moul committed
211
212
213
214
def check_port(port):
    try:
        port = int(port)
    except:
215
        print("Port must be an integer", file=stderr)
216
        return False
Moul's avatar
Moul committed
217
    if port < 0 or port > 65536:
218
        print("Wrong port number", file=stderr)
219
220
        return False
    return True
221

222

223
224
class HeadBlock(object):
    __instance = None
225

226
    def __new__(cls):
227
228
229
        if HeadBlock.__instance is None:
            HeadBlock.__instance = object.__new__(cls)
        return HeadBlock.__instance
230

231
232
    def __init__(self):
        self.head_block = get_request("blockchain/current")