network_tools.py 6.94 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
        self.ep = ep
115
116
        api = "BMAS" if ep["port"] == "443" else "BASIC_MERKLED_API"
        self.BMA_ENDPOINT = " ".join([api, ep["domain"], ep["port"]])
117
118


Moul's avatar
Moul committed
119
120
121
122
123
124
125
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
126
127
        if check_port(sep[-1]):
            ep["port"] = sep[-1]
Moul's avatar
Moul committed
128
129
130
131
132
133
        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
134
135
136
137
138
139
            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)
140
        return ep
141
    else:
142
        return None
Moul's avatar
Moul committed
143

Moul's avatar
Moul committed
144

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

Moul's avatar
Moul committed
155

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

Moul's avatar
Moul committed
162

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

Moul's avatar
Moul committed
175

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

Moul's avatar
Moul committed
192

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


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

224

225
226
class HeadBlock(object):
    __instance = None
227

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

233
234
    def __init__(self):
        self.head_block = get_request("blockchain/current")