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

network_tools.py 6.95 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
from ipaddress import ip_address
Moul's avatar
Moul committed
20
import socket
21
import logging
22
from sys import exit, stderr
23
from commandlines import Command
24
from duniterpy.api.client import Client
25
from duniterpy.api.bma import blockchain, network
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
async 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
42
    client = ClientInstance().client
    endpoints = await get_peers_among_leaves(client)
Moul's avatar
Moul committed
43
44
    if discover:
        print("Discovering network")
45
    for i, endpoint in enumerate(endpoints):
Moul's avatar
Moul committed
46
47
        if discover:
            print("{0:.0f}%".format(i / len(endpoints) * 100))
48
49
        if best_node(endpoint, False) is None:
            endpoints.remove(endpoint)
Moul's avatar
Moul committed
50
        elif discover:
51
            endpoints = await recursive_discovering(endpoints, endpoint)
52
    return endpoints
53

Moul's avatar
Moul committed
54

55
async def recursive_discovering(endpoints, endpoint):
56
57
58
59
    """
    Discover recursively new nodes.
    If new node found add it and try to found new node from his known nodes.
    """
60
61
62
63
    api = generate_duniterpy_endpoint_format(endpoint)
    sub_client = Client(api)
    news = await get_peers_among_leaves(sub_client)
    await sub_client.close()
64
    for new in news:
Moul's avatar
Moul committed
65
        if best_node(new, False) is not None and new not in endpoints:
66
            endpoints.append(new)
67
            await recursive_discovering(endpoints, new)
68
    return endpoints
Moul's avatar
Moul committed
69

Moul's avatar
Moul committed
70

71
72
73
74
75
76
77
78
79
80
81
82
async def get_peers_among_leaves(client):
    """
    Browse among leaves of peers to retrieve the other peers’ endpoints
    """
    leaves = await client(network.peers, leaves=True)
    peers = list()
    for leaf in leaves["leaves"]:
        leaf_response = await client(network.peers, leaf=leaf)
        peers.append(leaf_response["leaf"]["value"])
    return parse_endpoints(peers)


Moul's avatar
Moul committed
83
84
85
86
87
88
89
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
90
91
    while i < len(rep):
        if rep[i]["status"] == "UP":
Moul's avatar
Moul committed
92
93
            while j < len(rep[i]["endpoints"]):
                ep = parse_endpoint(rep[i]["endpoints"][j])
Moul's avatar
Moul committed
94
                j += 1
95
96
97
                if ep is None:
                    break
                ep["pubkey"] = rep[i]["pubkey"]
Moul's avatar
Moul committed
98
                endpoints.append(ep)
Moul's avatar
Moul committed
99
100
        i += 1
        j = 0
101
    return endpoints
Moul's avatar
Moul committed
102

Moul's avatar
Moul committed
103

104
105
106
107
108
109
110
111
112
def generate_duniterpy_endpoint_format(ep):
    api = "BASIC_MERKLED_API " if ep["port"] != "443" else "BMAS "
    api += ep.get("domain") + " " if "domain" in ep else ""
    api += ep.get("ip4") + " " if "ip4" in ep else ""
    api += ep.get("ip6") + " " if "ip6" in ep else ""
    api += ep.get("port")
    return api


113
114
115
116
117
118
119
def singleton(class_):
    instances = {}

    def getinstance(*args, **kwargs):
        if class_ not in instances:
            instances[class_] = class_(*args, **kwargs)
        return instances[class_]
120

121
    return getinstance
122

123
124
125

@singleton
class EndPoint(object):
126
127
128
    def __init__(self):
        cli_args = Command()
        ep = dict()
Moul's avatar
Moul committed
129
        if cli_args.contains_switches("p"):
130
131
132
133
134
            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
135
        else:
Moul's avatar
Moul committed
136
137
138
139
140
141
            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("]"):
142
            ep["domain"] = ep["domain"][1:-1]
143
        self.ep = ep
144
145
        api = "BMAS" if ep["port"] == "443" else "BASIC_MERKLED_API"
        self.BMA_ENDPOINT = " ".join([api, ep["domain"], ep["port"]])
146
147


148
149
150
151
152
153
@singleton
class ClientInstance(object):
    def __init__(self):
        self.client = Client(EndPoint().BMA_ENDPOINT)


Moul's avatar
Moul committed
154
155
156
157
158
159
160
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
161
162
        if check_port(sep[-1]):
            ep["port"] = sep[-1]
Moul's avatar
Moul committed
163
164
165
166
167
168
        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
169
170
171
172
173
174
            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)
175
        return ep
176
    else:
177
        return None
Moul's avatar
Moul committed
178

Moul's avatar
Moul committed
179

Moul's avatar
Moul committed
180
181
def endpoint_type(sep, ep):
    typ = check_ip(sep)
Moul's avatar
Moul committed
182
183
184
185
186
187
    if typ == 0:
        ep["domain"] = sep
    elif typ == 4:
        ep["ip4"] = sep
    elif typ == 6:
        ep["ip6"] = sep
188
    return ep
Moul's avatar
Moul committed
189

Moul's avatar
Moul committed
190

Moul's avatar
Moul committed
191
def check_ip(address):
Moul's avatar
Moul committed
192
    try:
193
        return ip_address(address).version
Moul's avatar
Moul committed
194
195
196
    except:
        return 0

Moul's avatar
Moul committed
197
198
199

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


Moul's avatar
Moul committed
218
219
220
221
def check_port(port):
    try:
        port = int(port)
    except:
222
        print("Port must be an integer", file=stderr)
223
        return False
Moul's avatar
Moul committed
224
    if port < 0 or port > 65536:
225
        print("Wrong port number", file=stderr)
226
227
        return False
    return True
228

229

230
231
class HeadBlock(object):
    __instance = None
232

233
    def __new__(cls):
234
235
236
        if HeadBlock.__instance is None:
            HeadBlock.__instance = object.__new__(cls)
        return HeadBlock.__instance
237

238
    def __init__(self):
Moul's avatar
Moul committed
239
240
241
242
243
        self.head_block = self.get_head()

    async def get_head(self):
        client = ClientInstance().client
        return await client(blockchain.current)