2018-10-10 12:23:21 +02:00
|
|
|
"""
|
2019-12-19 12:24:53 +01:00
|
|
|
TCP protocol handler
|
2018-10-10 12:23:21 +02:00
|
|
|
"""
|
2019-12-19 12:24:53 +01:00
|
|
|
# pylint: disable=too-many-ancestors
|
2020-08-31 11:25:53 +02:00
|
|
|
import l10n
|
2019-08-06 13:04:33 +02:00
|
|
|
import logging
|
2017-05-27 19:09:21 +02:00
|
|
|
import math
|
|
|
|
import random
|
2018-10-10 12:23:21 +02:00
|
|
|
import socket
|
|
|
|
import time
|
2017-05-27 19:09:21 +02:00
|
|
|
|
2018-10-10 12:23:21 +02:00
|
|
|
import addresses
|
2018-07-17 13:28:56 +02:00
|
|
|
import asyncore_pollchoose as asyncore
|
|
|
|
import connectionpool
|
2018-10-10 12:23:21 +02:00
|
|
|
import helper_random
|
|
|
|
import knownnodes
|
|
|
|
import protocol
|
|
|
|
import state
|
|
|
|
from bmconfigparser import BMConfigParser
|
2017-07-10 07:10:05 +02:00
|
|
|
from helper_random import randomBytes
|
2017-05-27 19:09:21 +02:00
|
|
|
from inventory import Inventory
|
|
|
|
from network.advanceddispatcher import AdvancedDispatcher
|
2019-11-27 06:47:04 +01:00
|
|
|
from network.assemble import assemble_addr
|
2018-10-10 12:23:21 +02:00
|
|
|
from network.bmproto import BMProto
|
2019-11-27 06:47:04 +01:00
|
|
|
from network.constants import MAX_OBJECT_COUNT
|
2017-10-20 01:21:49 +02:00
|
|
|
from network.dandelion import Dandelion
|
2017-05-27 19:09:21 +02:00
|
|
|
from network.objectracker import ObjectTracker
|
2018-10-10 12:23:21 +02:00
|
|
|
from network.socks4a import Socks4aConnection
|
|
|
|
from network.socks5 import Socks5Connection
|
2017-05-27 19:09:21 +02:00
|
|
|
from network.tls import TLSDispatcher
|
2019-11-03 16:11:52 +01:00
|
|
|
from node import Peer
|
2019-12-19 12:24:53 +01:00
|
|
|
from queues import invQueue, receiveDataQueue, UISignalQueue
|
2020-08-31 11:25:53 +02:00
|
|
|
from tr import _translate
|
2017-05-27 19:09:21 +02:00
|
|
|
|
2019-08-06 13:04:33 +02:00
|
|
|
logger = logging.getLogger('default')
|
|
|
|
|
2017-05-27 19:09:21 +02:00
|
|
|
|
2019-11-05 17:54:04 +01:00
|
|
|
maximumAgeOfNodesThatIAdvertiseToOthers = 10800 #: Equals three hours
|
2020-08-31 11:25:53 +02:00
|
|
|
maximumTimeOffsetWrongCount = 3 #: Connections with wrong time offset
|
2019-11-05 17:54:04 +01:00
|
|
|
|
|
|
|
|
2018-07-17 13:28:56 +02:00
|
|
|
class TCPConnection(BMProto, TLSDispatcher):
|
|
|
|
# pylint: disable=too-many-instance-attributes
|
2018-10-10 12:23:21 +02:00
|
|
|
"""
|
|
|
|
.. todo:: Look to understand and/or fix the non-parent-init-called
|
|
|
|
"""
|
|
|
|
|
2017-05-27 19:09:21 +02:00
|
|
|
def __init__(self, address=None, sock=None):
|
2017-05-29 00:24:07 +02:00
|
|
|
BMProto.__init__(self, address=address, sock=sock)
|
2017-05-27 19:09:21 +02:00
|
|
|
self.verackReceived = False
|
|
|
|
self.verackSent = False
|
|
|
|
self.streams = [0]
|
|
|
|
self.fullyEstablished = False
|
|
|
|
self.connectedAt = 0
|
|
|
|
self.skipUntil = 0
|
|
|
|
if address is None and sock is not None:
|
2019-11-03 16:11:52 +01:00
|
|
|
self.destination = Peer(*sock.getpeername())
|
2017-05-27 19:09:21 +02:00
|
|
|
self.isOutbound = False
|
|
|
|
TLSDispatcher.__init__(self, sock, server_side=True)
|
|
|
|
self.connectedAt = time.time()
|
2018-07-17 13:28:56 +02:00
|
|
|
logger.debug(
|
|
|
|
'Received connection from %s:%i',
|
|
|
|
self.destination.host, self.destination.port)
|
2017-07-10 07:10:05 +02:00
|
|
|
self.nodeid = randomBytes(8)
|
2017-06-10 10:13:49 +02:00
|
|
|
elif address is not None and sock is not None:
|
|
|
|
TLSDispatcher.__init__(self, sock, server_side=False)
|
|
|
|
self.isOutbound = True
|
2018-07-17 13:28:56 +02:00
|
|
|
logger.debug(
|
|
|
|
'Outbound proxy connection to %s:%i',
|
|
|
|
self.destination.host, self.destination.port)
|
2017-05-27 19:09:21 +02:00
|
|
|
else:
|
|
|
|
self.destination = address
|
|
|
|
self.isOutbound = True
|
2018-07-17 13:28:56 +02:00
|
|
|
self.create_socket(
|
|
|
|
socket.AF_INET6 if ":" in address.host else socket.AF_INET,
|
|
|
|
socket.SOCK_STREAM)
|
2017-05-27 19:09:21 +02:00
|
|
|
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
|
|
TLSDispatcher.__init__(self, sock, server_side=False)
|
|
|
|
self.connect(self.destination)
|
2018-07-17 13:28:56 +02:00
|
|
|
logger.debug(
|
|
|
|
'Connecting to %s:%i',
|
|
|
|
self.destination.host, self.destination.port)
|
2019-07-12 17:19:06 +02:00
|
|
|
try:
|
|
|
|
self.local = (
|
|
|
|
protocol.checkIPAddress(
|
2020-06-09 21:37:02 +02:00
|
|
|
protocol.encodeHost(self.destination.host), True)
|
|
|
|
and not protocol.checkSocksIP(self.destination.host)
|
2019-07-12 17:19:06 +02:00
|
|
|
)
|
|
|
|
except socket.error:
|
2019-12-19 12:24:53 +01:00
|
|
|
# it's probably a hostname
|
|
|
|
pass
|
2019-11-16 11:52:36 +01:00
|
|
|
self.network_group = protocol.network_group(self.destination.host)
|
2018-10-10 12:23:21 +02:00
|
|
|
ObjectTracker.__init__(self) # pylint: disable=non-parent-init-called
|
2017-05-27 19:09:21 +02:00
|
|
|
self.bm_proto_reset()
|
2017-05-31 10:17:36 +02:00
|
|
|
self.set_state("bm_header", expectBytes=protocol.Header.size)
|
2017-05-27 19:09:21 +02:00
|
|
|
|
2018-10-10 12:23:21 +02:00
|
|
|
def antiIntersectionDelay(self, initial=False):
|
|
|
|
"""
|
|
|
|
This is a defense against the so called intersection attacks.
|
|
|
|
|
2018-07-17 13:28:56 +02:00
|
|
|
It is called when you notice peer is requesting non-existing
|
|
|
|
objects, or right after the connection is established. It will
|
|
|
|
estimate how long an object will take to propagate across the
|
|
|
|
network, and skip processing "getdata" requests until then. This
|
|
|
|
means an attacker only has one shot per IP to perform the attack.
|
2018-10-10 12:23:21 +02:00
|
|
|
"""
|
2018-07-17 13:28:56 +02:00
|
|
|
# estimated time for a small object to propagate across the
|
|
|
|
# whole network
|
|
|
|
max_known_nodes = max(
|
|
|
|
len(knownnodes.knownNodes[x]) for x in knownnodes.knownNodes)
|
|
|
|
delay = math.ceil(math.log(max_known_nodes + 2, 20)) * (
|
|
|
|
0.2 + invQueue.queueCount / 2.0)
|
2017-05-27 19:09:21 +02:00
|
|
|
# take the stream with maximum amount of nodes
|
|
|
|
# +2 is to avoid problems with log(0) and log(1)
|
|
|
|
# 20 is avg connected nodes count
|
|
|
|
# 0.2 is avg message transmission time
|
|
|
|
if delay > 0:
|
|
|
|
if initial:
|
|
|
|
self.skipUntil = self.connectedAt + delay
|
|
|
|
if self.skipUntil > time.time():
|
2018-07-17 13:28:56 +02:00
|
|
|
logger.debug(
|
|
|
|
'Initial skipping processing getdata for %.2fs',
|
|
|
|
self.skipUntil - time.time())
|
2017-05-27 19:09:21 +02:00
|
|
|
else:
|
2018-07-17 13:28:56 +02:00
|
|
|
logger.debug(
|
|
|
|
'Skipping processing getdata due to missing object'
|
|
|
|
' for %.2fs', delay)
|
2017-05-27 19:09:21 +02:00
|
|
|
self.skipUntil = time.time() + delay
|
|
|
|
|
2020-08-31 11:25:53 +02:00
|
|
|
def checkTimeOffsetNotification(self):
|
|
|
|
"""
|
|
|
|
Check if we have connected to too many nodes which have too high
|
|
|
|
time offset from us
|
|
|
|
"""
|
|
|
|
if BMProto.timeOffsetWrongCount > \
|
|
|
|
maximumTimeOffsetWrongCount and \
|
|
|
|
not self.fullyEstablished:
|
|
|
|
UISignalQueue.put((
|
|
|
|
'updateStatusBar',
|
|
|
|
_translate(
|
|
|
|
"MainWindow",
|
|
|
|
"The time on your computer, %1, may be wrong. "
|
|
|
|
"Please verify your settings."
|
|
|
|
).arg(l10n.formatTimestamp())))
|
|
|
|
|
2017-07-06 19:45:36 +02:00
|
|
|
def state_connection_fully_established(self):
|
2018-10-10 12:23:21 +02:00
|
|
|
"""
|
2018-07-17 13:28:56 +02:00
|
|
|
State after the bitmessage protocol handshake is completed
|
|
|
|
(version/verack exchange, and if both side support TLS,
|
|
|
|
the TLS handshake as well).
|
2018-10-10 12:23:21 +02:00
|
|
|
"""
|
2017-07-06 19:45:36 +02:00
|
|
|
self.set_connection_fully_established()
|
|
|
|
self.set_state("bm_header")
|
|
|
|
self.bm_proto_reset()
|
|
|
|
return True
|
|
|
|
|
2017-05-27 19:09:21 +02:00
|
|
|
def set_connection_fully_established(self):
|
2018-10-10 12:23:21 +02:00
|
|
|
"""Initiate inventory synchronisation."""
|
2017-05-29 00:24:07 +02:00
|
|
|
if not self.isOutbound and not self.local:
|
2019-11-05 17:54:04 +01:00
|
|
|
state.clientHasReceivedIncomingConnections = True
|
2017-05-29 00:24:07 +02:00
|
|
|
UISignalQueue.put(('setStatusIcon', 'green'))
|
2020-06-09 11:54:55 +02:00
|
|
|
UISignalQueue.put((
|
|
|
|
'updateNetworkStatusTab', (self.isOutbound, True, self.destination)
|
|
|
|
))
|
2017-05-27 19:09:21 +02:00
|
|
|
self.antiIntersectionDelay(True)
|
|
|
|
self.fullyEstablished = True
|
2020-06-09 11:54:55 +02:00
|
|
|
# The connection having host suitable for knownnodes
|
|
|
|
if self.isOutbound or not self.local and not state.socksIP:
|
2017-07-05 09:17:01 +02:00
|
|
|
knownnodes.increaseRating(self.destination)
|
2020-06-10 12:22:49 +02:00
|
|
|
knownnodes.addKnownNode(
|
|
|
|
self.streams, self.destination, time.time())
|
2017-10-20 01:21:49 +02:00
|
|
|
Dandelion().maybeAddStem(self)
|
2017-05-27 19:09:21 +02:00
|
|
|
self.sendAddr()
|
|
|
|
self.sendBigInv()
|
|
|
|
|
|
|
|
def sendAddr(self):
|
2018-10-10 12:23:21 +02:00
|
|
|
"""Send a partial list of known addresses to peer."""
|
2017-05-27 19:09:21 +02:00
|
|
|
# We are going to share a maximum number of 1000 addrs (per overlapping
|
|
|
|
# stream) with our peer. 500 from overlapping streams, 250 from the
|
|
|
|
# left child stream, and 250 from the right child stream.
|
2018-10-30 17:11:59 +01:00
|
|
|
maxAddrCount = BMConfigParser().safeGetInt(
|
|
|
|
"bitmessagesettings", "maxaddrperstreamsend", 500)
|
2017-05-27 19:09:21 +02:00
|
|
|
|
|
|
|
templist = []
|
|
|
|
addrs = {}
|
|
|
|
for stream in self.streams:
|
|
|
|
with knownnodes.knownNodesLock:
|
2018-10-30 17:11:59 +01:00
|
|
|
for n, s in enumerate((stream, stream * 2, stream * 2 + 1)):
|
|
|
|
nodes = knownnodes.knownNodes.get(s)
|
|
|
|
if not nodes:
|
|
|
|
continue
|
2017-05-27 19:09:21 +02:00
|
|
|
# only if more recent than 3 hours
|
2018-10-30 17:11:59 +01:00
|
|
|
# and having positive or neutral rating
|
|
|
|
filtered = [
|
|
|
|
(k, v) for k, v in nodes.iteritems()
|
2020-06-09 21:37:02 +02:00
|
|
|
if v["lastseen"] > int(time.time())
|
|
|
|
- maximumAgeOfNodesThatIAdvertiseToOthers
|
|
|
|
and v["rating"] >= 0 and len(k.host) <= 22
|
2018-10-30 17:11:59 +01:00
|
|
|
]
|
|
|
|
# sent 250 only if the remote isn't interested in it
|
|
|
|
elemCount = min(
|
|
|
|
len(filtered),
|
|
|
|
maxAddrCount / 2 if n else maxAddrCount)
|
|
|
|
addrs[s] = helper_random.randomsample(filtered, elemCount)
|
2018-10-10 12:23:21 +02:00
|
|
|
for substream in addrs:
|
2017-07-05 09:17:01 +02:00
|
|
|
for peer, params in addrs[substream]:
|
|
|
|
templist.append((substream, peer, params["lastseen"]))
|
2018-10-10 12:23:21 +02:00
|
|
|
if templist:
|
2019-11-27 06:47:04 +01:00
|
|
|
self.append_write_buf(assemble_addr(templist))
|
2017-05-27 19:09:21 +02:00
|
|
|
|
|
|
|
def sendBigInv(self):
|
2018-07-17 13:28:56 +02:00
|
|
|
"""
|
|
|
|
Send hashes of all inventory objects, chunked as the protocol has
|
|
|
|
a per-command limit.
|
|
|
|
"""
|
2017-07-06 19:45:36 +02:00
|
|
|
def sendChunk():
|
2018-10-10 12:23:21 +02:00
|
|
|
"""Send one chunk of inv entries in one command"""
|
2017-07-06 19:45:36 +02:00
|
|
|
if objectCount == 0:
|
|
|
|
return
|
2018-07-17 13:28:56 +02:00
|
|
|
logger.debug(
|
|
|
|
'Sending huge inv message with %i objects to just this'
|
|
|
|
' one peer', objectCount)
|
|
|
|
self.append_write_buf(protocol.CreatePacket(
|
|
|
|
'inv', addresses.encodeVarint(objectCount) + payload))
|
2017-07-06 19:45:36 +02:00
|
|
|
|
|
|
|
# Select all hashes for objects in this stream.
|
|
|
|
bigInvList = {}
|
|
|
|
for stream in self.streams:
|
2018-07-17 13:28:56 +02:00
|
|
|
# may lock for a long time, but I think it's better than
|
|
|
|
# thousands of small locks
|
2017-07-06 19:45:36 +02:00
|
|
|
with self.objectsNewToThemLock:
|
|
|
|
for objHash in Inventory().unexpired_hashes_by_stream(stream):
|
2017-09-25 01:17:04 +02:00
|
|
|
# don't advertise stem objects on bigInv
|
2018-02-03 11:46:39 +01:00
|
|
|
if Dandelion().hasHash(objHash):
|
2017-09-25 01:17:04 +02:00
|
|
|
continue
|
2017-07-06 19:45:36 +02:00
|
|
|
bigInvList[objHash] = 0
|
|
|
|
objectCount = 0
|
|
|
|
payload = b''
|
2019-12-19 12:24:53 +01:00
|
|
|
# Now let us start appending all of these hashes together.
|
|
|
|
# They will be sent out in a big inv message to our new peer.
|
2018-10-10 12:23:21 +02:00
|
|
|
for obj_hash, _ in bigInvList.items():
|
|
|
|
payload += obj_hash
|
2017-07-06 19:45:36 +02:00
|
|
|
objectCount += 1
|
2018-06-14 12:47:46 +02:00
|
|
|
|
|
|
|
# Remove -1 below when sufficient time has passed for users to
|
|
|
|
# upgrade to versions of PyBitmessage that accept inv with 50,000
|
|
|
|
# items
|
2019-11-27 06:47:04 +01:00
|
|
|
if objectCount >= MAX_OBJECT_COUNT - 1:
|
2017-12-29 08:41:15 +01:00
|
|
|
sendChunk()
|
2017-07-06 19:45:36 +02:00
|
|
|
payload = b''
|
|
|
|
objectCount = 0
|
|
|
|
|
|
|
|
# flush
|
|
|
|
sendChunk()
|
2017-05-27 19:09:21 +02:00
|
|
|
|
2017-06-03 16:30:05 +02:00
|
|
|
def handle_connect(self):
|
2018-10-10 12:23:21 +02:00
|
|
|
"""Callback for TCP connection being established."""
|
2017-05-27 19:09:21 +02:00
|
|
|
try:
|
2017-06-03 16:30:05 +02:00
|
|
|
AdvancedDispatcher.handle_connect(self)
|
2017-05-27 19:09:21 +02:00
|
|
|
except socket.error as e:
|
2018-07-17 13:28:56 +02:00
|
|
|
# pylint: disable=protected-access
|
|
|
|
if e.errno in asyncore._DISCONNECTED:
|
|
|
|
logger.debug(
|
|
|
|
'%s:%i: Connection failed: %s',
|
|
|
|
self.destination.host, self.destination.port, e)
|
2017-05-27 21:52:56 +02:00
|
|
|
return
|
2017-07-10 07:10:05 +02:00
|
|
|
self.nodeid = randomBytes(8)
|
2018-10-10 12:23:21 +02:00
|
|
|
self.append_write_buf(
|
|
|
|
protocol.assembleVersionMessage(
|
2018-07-17 13:28:56 +02:00
|
|
|
self.destination.host, self.destination.port,
|
|
|
|
connectionpool.BMConnectionPool().streams,
|
|
|
|
False, nodeid=self.nodeid))
|
2017-05-27 21:52:56 +02:00
|
|
|
self.connectedAt = time.time()
|
2017-07-08 06:54:25 +02:00
|
|
|
receiveDataQueue.put(self.destination)
|
2017-05-27 21:52:56 +02:00
|
|
|
|
|
|
|
def handle_read(self):
|
2018-10-10 12:23:21 +02:00
|
|
|
"""Callback for reading from a socket"""
|
2017-06-10 10:13:49 +02:00
|
|
|
TLSDispatcher.handle_read(self)
|
2017-07-08 06:54:25 +02:00
|
|
|
receiveDataQueue.put(self.destination)
|
2017-05-27 19:09:21 +02:00
|
|
|
|
2017-05-27 21:52:56 +02:00
|
|
|
def handle_write(self):
|
2018-10-10 12:23:21 +02:00
|
|
|
"""Callback for writing to a socket"""
|
2017-06-10 10:13:49 +02:00
|
|
|
TLSDispatcher.handle_write(self)
|
2017-07-05 09:17:01 +02:00
|
|
|
|
2017-10-19 09:08:05 +02:00
|
|
|
def handle_close(self):
|
2018-10-10 12:23:21 +02:00
|
|
|
"""Callback for connection being closed."""
|
2020-06-09 11:54:55 +02:00
|
|
|
host_is_global = self.isOutbound or not self.local and not state.socksIP
|
2017-10-19 08:39:09 +02:00
|
|
|
if self.fullyEstablished:
|
2018-07-17 13:28:56 +02:00
|
|
|
UISignalQueue.put((
|
|
|
|
'updateNetworkStatusTab',
|
|
|
|
(self.isOutbound, False, self.destination)
|
|
|
|
))
|
2020-06-09 11:54:55 +02:00
|
|
|
if host_is_global:
|
2020-06-10 12:22:49 +02:00
|
|
|
knownnodes.addKnownNode(
|
|
|
|
self.streams, self.destination, time.time())
|
2017-10-20 01:21:49 +02:00
|
|
|
Dandelion().maybeRemoveStem(self)
|
2020-08-31 11:25:53 +02:00
|
|
|
else:
|
|
|
|
self.checkTimeOffsetNotification()
|
|
|
|
if host_is_global:
|
|
|
|
knownnodes.decreaseRating(self.destination)
|
2017-10-19 09:08:05 +02:00
|
|
|
BMProto.handle_close(self)
|
2017-05-27 19:09:21 +02:00
|
|
|
|
|
|
|
|
|
|
|
class Socks5BMConnection(Socks5Connection, TCPConnection):
|
2018-10-10 12:23:21 +02:00
|
|
|
"""SOCKS5 wrapper for TCP connections"""
|
|
|
|
|
2017-05-27 19:09:21 +02:00
|
|
|
def __init__(self, address):
|
|
|
|
Socks5Connection.__init__(self, address=address)
|
2017-06-10 10:13:49 +02:00
|
|
|
TCPConnection.__init__(self, address=address, sock=self.socket)
|
|
|
|
self.set_state("init")
|
2017-05-27 19:09:21 +02:00
|
|
|
|
2017-06-24 12:23:56 +02:00
|
|
|
def state_proxy_handshake_done(self):
|
2018-07-17 13:28:56 +02:00
|
|
|
"""
|
|
|
|
State when SOCKS5 connection succeeds, we need to send a
|
|
|
|
Bitmessage handshake to peer.
|
|
|
|
"""
|
2017-06-24 12:23:56 +02:00
|
|
|
Socks5Connection.state_proxy_handshake_done(self)
|
2017-07-10 07:10:05 +02:00
|
|
|
self.nodeid = randomBytes(8)
|
2018-10-10 12:23:21 +02:00
|
|
|
self.append_write_buf(
|
|
|
|
protocol.assembleVersionMessage(
|
2018-07-17 13:28:56 +02:00
|
|
|
self.destination.host, self.destination.port,
|
|
|
|
connectionpool.BMConnectionPool().streams,
|
|
|
|
False, nodeid=self.nodeid))
|
2017-06-10 10:13:49 +02:00
|
|
|
self.set_state("bm_header", expectBytes=protocol.Header.size)
|
2017-07-06 19:45:36 +02:00
|
|
|
return True
|
2017-05-27 19:09:21 +02:00
|
|
|
|
|
|
|
|
|
|
|
class Socks4aBMConnection(Socks4aConnection, TCPConnection):
|
2018-10-10 12:23:21 +02:00
|
|
|
"""SOCKS4a wrapper for TCP connections"""
|
|
|
|
|
2017-05-27 19:09:21 +02:00
|
|
|
def __init__(self, address):
|
|
|
|
Socks4aConnection.__init__(self, address=address)
|
2017-06-10 10:13:49 +02:00
|
|
|
TCPConnection.__init__(self, address=address, sock=self.socket)
|
|
|
|
self.set_state("init")
|
2017-05-27 19:09:21 +02:00
|
|
|
|
2017-06-24 12:23:56 +02:00
|
|
|
def state_proxy_handshake_done(self):
|
2018-07-17 13:28:56 +02:00
|
|
|
"""
|
|
|
|
State when SOCKS4a connection succeeds, we need to send a
|
|
|
|
Bitmessage handshake to peer.
|
|
|
|
"""
|
2017-06-24 12:23:56 +02:00
|
|
|
Socks4aConnection.state_proxy_handshake_done(self)
|
2017-07-10 07:10:05 +02:00
|
|
|
self.nodeid = randomBytes(8)
|
2018-10-10 12:23:21 +02:00
|
|
|
self.append_write_buf(
|
|
|
|
protocol.assembleVersionMessage(
|
2018-07-17 13:28:56 +02:00
|
|
|
self.destination.host, self.destination.port,
|
|
|
|
connectionpool.BMConnectionPool().streams,
|
|
|
|
False, nodeid=self.nodeid))
|
2017-06-10 10:13:49 +02:00
|
|
|
self.set_state("bm_header", expectBytes=protocol.Header.size)
|
2017-07-06 19:45:36 +02:00
|
|
|
return True
|
2017-05-27 19:09:21 +02:00
|
|
|
|
|
|
|
|
2019-07-20 10:41:49 +02:00
|
|
|
def bootstrap(connection_class):
|
|
|
|
"""Make bootstrapper class for connection type (connection_class)"""
|
|
|
|
class Bootstrapper(connection_class):
|
|
|
|
"""Base class for bootstrappers"""
|
|
|
|
_connection_base = connection_class
|
|
|
|
|
|
|
|
def __init__(self, host, port):
|
2019-11-03 16:11:52 +01:00
|
|
|
self._connection_base.__init__(self, Peer(host, port))
|
2019-07-20 10:41:49 +02:00
|
|
|
self.close_reason = self._succeed = False
|
|
|
|
|
|
|
|
def bm_command_addr(self):
|
|
|
|
"""
|
|
|
|
Got addr message - the bootstrap succeed.
|
|
|
|
Let BMProto process the addr message and switch state to 'close'
|
|
|
|
"""
|
|
|
|
BMProto.bm_command_addr(self)
|
|
|
|
self._succeed = True
|
|
|
|
self.close_reason = "Thanks for bootstrapping!"
|
|
|
|
self.set_state("close")
|
|
|
|
|
2020-11-08 15:51:18 +01:00
|
|
|
def set_connection_fully_established(self):
|
|
|
|
"""Only send addr here"""
|
|
|
|
# pylint: disable=attribute-defined-outside-init
|
|
|
|
self.fullyEstablished = True
|
|
|
|
self.sendAddr()
|
|
|
|
|
2019-07-20 10:41:49 +02:00
|
|
|
def handle_close(self):
|
|
|
|
"""
|
|
|
|
After closing the connection switch knownnodes.knownNodesActual
|
|
|
|
back to False if the bootstrapper failed.
|
|
|
|
"""
|
2020-11-08 15:51:18 +01:00
|
|
|
BMProto.handle_close(self)
|
2019-07-20 10:41:49 +02:00
|
|
|
if not self._succeed:
|
|
|
|
knownnodes.knownNodesActual = False
|
|
|
|
|
|
|
|
return Bootstrapper
|
|
|
|
|
|
|
|
|
2017-05-27 19:09:21 +02:00
|
|
|
class TCPServer(AdvancedDispatcher):
|
2018-10-10 12:23:21 +02:00
|
|
|
"""TCP connection server for Bitmessage protocol"""
|
|
|
|
|
2018-07-17 13:28:56 +02:00
|
|
|
def __init__(self, host='127.0.0.1', port=8444):
|
2017-05-27 19:09:21 +02:00
|
|
|
if not hasattr(self, '_map'):
|
|
|
|
AdvancedDispatcher.__init__(self)
|
|
|
|
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
|
|
self.set_reuse_addr()
|
2017-08-09 17:29:23 +02:00
|
|
|
for attempt in range(50):
|
|
|
|
try:
|
|
|
|
if attempt > 0:
|
2019-08-14 17:34:34 +02:00
|
|
|
logger.warning('Failed to bind on port %s', port)
|
2017-08-09 17:29:23 +02:00
|
|
|
port = random.randint(32767, 65535)
|
|
|
|
self.bind((host, port))
|
|
|
|
except socket.error as e:
|
|
|
|
if e.errno in (asyncore.EADDRINUSE, asyncore.WSAEADDRINUSE):
|
|
|
|
continue
|
|
|
|
else:
|
|
|
|
if attempt > 0:
|
2019-08-14 17:34:34 +02:00
|
|
|
logger.warning('Setting port to %s', port)
|
2018-07-17 13:28:56 +02:00
|
|
|
BMConfigParser().set(
|
|
|
|
'bitmessagesettings', 'port', str(port))
|
2017-08-09 17:29:23 +02:00
|
|
|
BMConfigParser().save()
|
|
|
|
break
|
2019-11-03 16:11:52 +01:00
|
|
|
self.destination = Peer(host, port)
|
2017-08-09 17:29:23 +02:00
|
|
|
self.bound = True
|
2017-05-27 19:09:21 +02:00
|
|
|
self.listen(5)
|
|
|
|
|
2017-08-09 17:29:23 +02:00
|
|
|
def is_bound(self):
|
2018-10-10 12:23:21 +02:00
|
|
|
"""Is the socket bound?"""
|
2017-08-09 17:29:23 +02:00
|
|
|
try:
|
|
|
|
return self.bound
|
|
|
|
except AttributeError:
|
|
|
|
return False
|
|
|
|
|
2017-05-27 19:09:21 +02:00
|
|
|
def handle_accept(self):
|
2018-10-10 12:23:21 +02:00
|
|
|
"""Incoming connection callback"""
|
2018-07-17 13:28:56 +02:00
|
|
|
try:
|
|
|
|
sock = self.accept()[0]
|
|
|
|
except (TypeError, IndexError):
|
|
|
|
return
|
|
|
|
|
2019-11-03 16:11:52 +01:00
|
|
|
state.ownAddresses[Peer(*sock.getsockname())] = True
|
2018-07-17 13:28:56 +02:00
|
|
|
if (
|
2020-06-09 22:00:42 +02:00
|
|
|
len(connectionpool.BMConnectionPool())
|
2020-06-09 21:37:02 +02:00
|
|
|
> BMConfigParser().safeGetInt(
|
|
|
|
'bitmessagesettings', 'maxtotalconnections')
|
|
|
|
+ BMConfigParser().safeGetInt(
|
|
|
|
'bitmessagesettings', 'maxbootstrapconnections') + 10
|
2018-07-17 13:28:56 +02:00
|
|
|
):
|
|
|
|
# 10 is a sort of buffer, in between it will go through
|
|
|
|
# the version handshake and return an error to the peer
|
|
|
|
logger.warning("Server full, dropping connection")
|
|
|
|
sock.close()
|
|
|
|
return
|
|
|
|
try:
|
|
|
|
connectionpool.BMConnectionPool().addConnection(
|
|
|
|
TCPConnection(sock=sock))
|
|
|
|
except socket.error:
|
|
|
|
pass
|