Start network subsystem with app object with config, state and protocol slots
This commit is contained in:
parent
ee97277d5c
commit
963ffd11f3
|
@ -21,6 +21,7 @@ app_dir = pathmagic.setup()
|
||||||
import depends
|
import depends
|
||||||
depends.check_dependencies()
|
depends.check_dependencies()
|
||||||
|
|
||||||
|
import collections
|
||||||
import getopt
|
import getopt
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
# Used to capture a Ctrl-C keypress so that Bitmessage can shutdown gracefully.
|
# Used to capture a Ctrl-C keypress so that Bitmessage can shutdown gracefully.
|
||||||
|
@ -30,6 +31,7 @@ import time
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
import defaults
|
import defaults
|
||||||
|
import protocol
|
||||||
import shared
|
import shared
|
||||||
import shutdown
|
import shutdown
|
||||||
import state
|
import state
|
||||||
|
@ -80,6 +82,9 @@ def signal_handler(signum, frame):
|
||||||
' because the UI captures the signal.')
|
' because the UI captures the signal.')
|
||||||
|
|
||||||
|
|
||||||
|
App = collections.namedtuple('App', ['state', 'config', 'protocol'])
|
||||||
|
|
||||||
|
|
||||||
class Main(object):
|
class Main(object):
|
||||||
"""Main PyBitmessage class"""
|
"""Main PyBitmessage class"""
|
||||||
def start(self):
|
def start(self):
|
||||||
|
@ -186,13 +191,13 @@ class Main(object):
|
||||||
shared.reloadBroadcastSendersForWhichImWatching()
|
shared.reloadBroadcastSendersForWhichImWatching()
|
||||||
|
|
||||||
# Start the address generation thread
|
# Start the address generation thread
|
||||||
addressGeneratorThread = addressGenerator(state)
|
addressGeneratorThread = addressGenerator()
|
||||||
# close the main program even if there are threads left
|
# close the main program even if there are threads left
|
||||||
addressGeneratorThread.daemon = True
|
addressGeneratorThread.daemon = True
|
||||||
addressGeneratorThread.start()
|
addressGeneratorThread.start()
|
||||||
|
|
||||||
# Start the thread that calculates POWs
|
# Start the thread that calculates POWs
|
||||||
singleWorkerThread = singleWorker(state)
|
singleWorkerThread = singleWorker()
|
||||||
# close the main program even if there are threads left
|
# close the main program even if there are threads left
|
||||||
singleWorkerThread.daemon = True
|
singleWorkerThread.daemon = True
|
||||||
singleWorkerThread.start()
|
singleWorkerThread.start()
|
||||||
|
@ -209,26 +214,26 @@ class Main(object):
|
||||||
if daemon and config.safeGet(
|
if daemon and config.safeGet(
|
||||||
'bitmessagesettings', 'smtpdeliver', '') != '':
|
'bitmessagesettings', 'smtpdeliver', '') != '':
|
||||||
from class_smtpDeliver import smtpDeliver
|
from class_smtpDeliver import smtpDeliver
|
||||||
smtpDeliveryThread = smtpDeliver(state)
|
smtpDeliveryThread = smtpDeliver()
|
||||||
smtpDeliveryThread.start()
|
smtpDeliveryThread.start()
|
||||||
|
|
||||||
# SMTP daemon thread
|
# SMTP daemon thread
|
||||||
if daemon and config.safeGetBoolean(
|
if daemon and config.safeGetBoolean(
|
||||||
'bitmessagesettings', 'smtpd'):
|
'bitmessagesettings', 'smtpd'):
|
||||||
from class_smtpServer import smtpServer
|
from class_smtpServer import smtpServer
|
||||||
smtpServerThread = smtpServer(state)
|
smtpServerThread = smtpServer()
|
||||||
smtpServerThread.start()
|
smtpServerThread.start()
|
||||||
|
|
||||||
# API is also objproc dependent
|
# API is also objproc dependent
|
||||||
if config.safeGetBoolean('bitmessagesettings', 'apienabled'):
|
if config.safeGetBoolean('bitmessagesettings', 'apienabled'):
|
||||||
import api # pylint: disable=relative-import
|
from api import singleAPI
|
||||||
singleAPIThread = api.singleAPI(state)
|
singleAPIThread = singleAPI()
|
||||||
# close the main program even if there are threads left
|
# close the main program even if there are threads left
|
||||||
singleAPIThread.daemon = True
|
singleAPIThread.daemon = True
|
||||||
singleAPIThread.start()
|
singleAPIThread.start()
|
||||||
|
|
||||||
# Start the cleanerThread
|
# Start the cleanerThread
|
||||||
singleCleanerThread = singleCleaner(state)
|
singleCleanerThread = singleCleaner()
|
||||||
# close the main program even if there are threads left
|
# close the main program even if there are threads left
|
||||||
singleCleanerThread.daemon = True
|
singleCleanerThread.daemon = True
|
||||||
singleCleanerThread.start()
|
singleCleanerThread.start()
|
||||||
|
@ -236,7 +241,7 @@ class Main(object):
|
||||||
# start network components if networking is enabled
|
# start network components if networking is enabled
|
||||||
if state.enableNetwork:
|
if state.enableNetwork:
|
||||||
start_proxyconfig()
|
start_proxyconfig()
|
||||||
network.start(config, state)
|
network.start(App(state, config, protocol))
|
||||||
|
|
||||||
if config.safeGetBoolean('bitmessagesettings', 'upnp'):
|
if config.safeGetBoolean('bitmessagesettings', 'upnp'):
|
||||||
import upnp
|
import upnp
|
||||||
|
|
|
@ -9,6 +9,7 @@ import defaults
|
||||||
import highlevelcrypto
|
import highlevelcrypto
|
||||||
import queues
|
import queues
|
||||||
import shared
|
import shared
|
||||||
|
import state
|
||||||
import tr
|
import tr
|
||||||
from addresses import decodeAddress, encodeAddress, encodeVarint
|
from addresses import decodeAddress, encodeAddress, encodeVarint
|
||||||
from bmconfigparser import BMConfigParser
|
from bmconfigparser import BMConfigParser
|
||||||
|
@ -44,7 +45,7 @@ class addressGenerator(StoppableThread):
|
||||||
"""
|
"""
|
||||||
# pylint: disable=too-many-locals, too-many-branches
|
# pylint: disable=too-many-locals, too-many-branches
|
||||||
# pylint: disable=protected-access, too-many-statements
|
# pylint: disable=protected-access, too-many-statements
|
||||||
while self.state.shutdown == 0:
|
while state.shutdown == 0:
|
||||||
queueValue = queues.addressGeneratorQueue.get()
|
queueValue = queues.addressGeneratorQueue.get()
|
||||||
nonceTrialsPerByte = 0
|
nonceTrialsPerByte = 0
|
||||||
payloadLengthExtraBytes = 0
|
payloadLengthExtraBytes = 0
|
||||||
|
|
|
@ -23,6 +23,7 @@ import proofofwork
|
||||||
import protocol
|
import protocol
|
||||||
import queues
|
import queues
|
||||||
import shared
|
import shared
|
||||||
|
import state
|
||||||
import tr
|
import tr
|
||||||
from addresses import (
|
from addresses import (
|
||||||
calculateInventoryHash, decodeAddress, decodeVarint, encodeVarint
|
calculateInventoryHash, decodeAddress, decodeVarint, encodeVarint
|
||||||
|
@ -47,8 +48,8 @@ def sizeof_fmt(num, suffix='h/s'):
|
||||||
class singleWorker(StoppableThread):
|
class singleWorker(StoppableThread):
|
||||||
"""Thread for performing PoW"""
|
"""Thread for performing PoW"""
|
||||||
|
|
||||||
def __init__(self, state):
|
def __init__(self):
|
||||||
super(singleWorker, self).__init__(state, name="singleWorker")
|
super(singleWorker, self).__init__(name="singleWorker")
|
||||||
proofofwork.init()
|
proofofwork.init()
|
||||||
|
|
||||||
def stopThread(self):
|
def stopThread(self):
|
||||||
|
@ -63,9 +64,9 @@ class singleWorker(StoppableThread):
|
||||||
def run(self):
|
def run(self):
|
||||||
# pylint: disable=attribute-defined-outside-init
|
# pylint: disable=attribute-defined-outside-init
|
||||||
|
|
||||||
while not helper_sql.sql_ready.wait(1.0) and self.state.shutdown == 0:
|
while not helper_sql.sql_ready.wait(1.0) and state.shutdown == 0:
|
||||||
self.stop.wait(1.0)
|
self.stop.wait(1.0)
|
||||||
if self.state.shutdown > 0:
|
if state.shutdown > 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Initialize the neededPubkeys dictionary.
|
# Initialize the neededPubkeys dictionary.
|
||||||
|
@ -78,7 +79,7 @@ class singleWorker(StoppableThread):
|
||||||
_, toAddressVersionNumber, toStreamNumber, toRipe = \
|
_, toAddressVersionNumber, toStreamNumber, toRipe = \
|
||||||
decodeAddress(toAddress)
|
decodeAddress(toAddress)
|
||||||
if toAddressVersionNumber <= 3:
|
if toAddressVersionNumber <= 3:
|
||||||
self.state.neededPubkeys[toAddress] = 0
|
state.neededPubkeys[toAddress] = 0
|
||||||
elif toAddressVersionNumber >= 4:
|
elif toAddressVersionNumber >= 4:
|
||||||
doubleHashOfAddressData = hashlib.sha512(hashlib.sha512(
|
doubleHashOfAddressData = hashlib.sha512(hashlib.sha512(
|
||||||
encodeVarint(toAddressVersionNumber)
|
encodeVarint(toAddressVersionNumber)
|
||||||
|
@ -89,7 +90,7 @@ class singleWorker(StoppableThread):
|
||||||
tag = doubleHashOfAddressData[32:]
|
tag = doubleHashOfAddressData[32:]
|
||||||
# We'll need this for when we receive a pubkey reply:
|
# We'll need this for when we receive a pubkey reply:
|
||||||
# it will be encrypted and we'll need to decrypt it.
|
# it will be encrypted and we'll need to decrypt it.
|
||||||
self.state.neededPubkeys[tag] = (
|
state.neededPubkeys[tag] = (
|
||||||
toAddress,
|
toAddress,
|
||||||
highlevelcrypto.makeCryptor(
|
highlevelcrypto.makeCryptor(
|
||||||
hexlify(privEncryptionKey))
|
hexlify(privEncryptionKey))
|
||||||
|
@ -101,19 +102,19 @@ class singleWorker(StoppableThread):
|
||||||
for row in queryreturn:
|
for row in queryreturn:
|
||||||
ackdata, = row
|
ackdata, = row
|
||||||
self.logger.info('Watching for ackdata %s', hexlify(ackdata))
|
self.logger.info('Watching for ackdata %s', hexlify(ackdata))
|
||||||
self.state.ackdataForWhichImWatching[ackdata] = 0
|
state.ackdataForWhichImWatching[ackdata] = 0
|
||||||
|
|
||||||
# Fix legacy (headerless) watched ackdata to include header
|
# Fix legacy (headerless) watched ackdata to include header
|
||||||
for oldack in self.state.ackdataForWhichImWatching:
|
for oldack in state.ackdataForWhichImWatching:
|
||||||
if len(oldack) == 32:
|
if len(oldack) == 32:
|
||||||
# attach legacy header, always constant (msg/1/1)
|
# attach legacy header, always constant (msg/1/1)
|
||||||
newack = '\x00\x00\x00\x02\x01\x01' + oldack
|
newack = '\x00\x00\x00\x02\x01\x01' + oldack
|
||||||
self.state.ackdataForWhichImWatching[newack] = 0
|
state.ackdataForWhichImWatching[newack] = 0
|
||||||
sqlExecute(
|
sqlExecute(
|
||||||
'''UPDATE sent SET ackdata=? WHERE ackdata=? AND folder = 'sent' ''',
|
'''UPDATE sent SET ackdata=? WHERE ackdata=? AND folder = 'sent' ''',
|
||||||
newack, oldack
|
newack, oldack
|
||||||
)
|
)
|
||||||
del self.state.ackdataForWhichImWatching[oldack]
|
del state.ackdataForWhichImWatching[oldack]
|
||||||
|
|
||||||
# For the case if user deleted knownnodes
|
# For the case if user deleted knownnodes
|
||||||
# but is still having onionpeer objects in inventory
|
# but is still having onionpeer objects in inventory
|
||||||
|
@ -128,7 +129,7 @@ class singleWorker(StoppableThread):
|
||||||
# before we start on existing POW tasks.
|
# before we start on existing POW tasks.
|
||||||
self.stop.wait(10)
|
self.stop.wait(10)
|
||||||
|
|
||||||
if self.state.shutdown:
|
if state.shutdown:
|
||||||
return
|
return
|
||||||
|
|
||||||
# just in case there are any pending tasks for msg
|
# just in case there are any pending tasks for msg
|
||||||
|
@ -141,7 +142,7 @@ class singleWorker(StoppableThread):
|
||||||
# send onionpeer object
|
# send onionpeer object
|
||||||
queues.workerQueue.put(('sendOnionPeerObj', ''))
|
queues.workerQueue.put(('sendOnionPeerObj', ''))
|
||||||
|
|
||||||
while self.state.shutdown == 0:
|
while state.shutdown == 0:
|
||||||
self.busy = 0
|
self.busy = 0
|
||||||
command, data = queues.workerQueue.get()
|
command, data = queues.workerQueue.get()
|
||||||
self.busy = 1
|
self.busy = 1
|
||||||
|
@ -493,7 +494,7 @@ class singleWorker(StoppableThread):
|
||||||
def sendOnionPeerObj(self, peer=None):
|
def sendOnionPeerObj(self, peer=None):
|
||||||
"""Send onionpeer object representing peer"""
|
"""Send onionpeer object representing peer"""
|
||||||
if not peer: # find own onionhostname
|
if not peer: # find own onionhostname
|
||||||
for peer in self.state.ownAddresses:
|
for peer in state.ownAddresses:
|
||||||
if peer.host.endswith('.onion'):
|
if peer.host.endswith('.onion'):
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
|
@ -798,8 +799,8 @@ class singleWorker(StoppableThread):
|
||||||
encodeVarint(toAddressVersionNumber)
|
encodeVarint(toAddressVersionNumber)
|
||||||
+ encodeVarint(toStreamNumber) + toRipe
|
+ encodeVarint(toStreamNumber) + toRipe
|
||||||
).digest()).digest()[32:]
|
).digest()).digest()[32:]
|
||||||
if toaddress in self.state.neededPubkeys or \
|
if toaddress in state.neededPubkeys or \
|
||||||
toTag in self.state.neededPubkeys:
|
toTag in state.neededPubkeys:
|
||||||
# We already sent a request for the pubkey
|
# We already sent a request for the pubkey
|
||||||
sqlExecute(
|
sqlExecute(
|
||||||
'''UPDATE sent SET status='awaitingpubkey', '''
|
'''UPDATE sent SET status='awaitingpubkey', '''
|
||||||
|
@ -840,7 +841,7 @@ class singleWorker(StoppableThread):
|
||||||
privEncryptionKey = doubleHashOfToAddressData[:32]
|
privEncryptionKey = doubleHashOfToAddressData[:32]
|
||||||
# The second half of the sha512 hash.
|
# The second half of the sha512 hash.
|
||||||
tag = doubleHashOfToAddressData[32:]
|
tag = doubleHashOfToAddressData[32:]
|
||||||
self.state.neededPubkeys[tag] = (
|
state.neededPubkeys[tag] = (
|
||||||
toaddress,
|
toaddress,
|
||||||
highlevelcrypto.makeCryptor(
|
highlevelcrypto.makeCryptor(
|
||||||
hexlify(privEncryptionKey))
|
hexlify(privEncryptionKey))
|
||||||
|
@ -863,7 +864,7 @@ class singleWorker(StoppableThread):
|
||||||
''' status='doingpubkeypow') AND '''
|
''' status='doingpubkeypow') AND '''
|
||||||
''' folder='sent' ''',
|
''' folder='sent' ''',
|
||||||
toaddress)
|
toaddress)
|
||||||
del self.state.neededPubkeys[tag]
|
del state.neededPubkeys[tag]
|
||||||
break
|
break
|
||||||
# else:
|
# else:
|
||||||
# There was something wrong with this
|
# There was something wrong with this
|
||||||
|
@ -905,7 +906,7 @@ class singleWorker(StoppableThread):
|
||||||
|
|
||||||
# if we aren't sending this to ourselves or a chan
|
# if we aren't sending this to ourselves or a chan
|
||||||
if not BMConfigParser().has_section(toaddress):
|
if not BMConfigParser().has_section(toaddress):
|
||||||
self.state.ackdataForWhichImWatching[ackdata] = 0
|
state.ackdataForWhichImWatching[ackdata] = 0
|
||||||
queues.UISignalQueue.put((
|
queues.UISignalQueue.put((
|
||||||
'updateSentItemStatusByAckdata', (
|
'updateSentItemStatusByAckdata', (
|
||||||
ackdata,
|
ackdata,
|
||||||
|
@ -1399,7 +1400,7 @@ class singleWorker(StoppableThread):
|
||||||
retryNumber = queryReturn[0][0]
|
retryNumber = queryReturn[0][0]
|
||||||
|
|
||||||
if addressVersionNumber <= 3:
|
if addressVersionNumber <= 3:
|
||||||
self.state.neededPubkeys[toAddress] = 0
|
state.neededPubkeys[toAddress] = 0
|
||||||
elif addressVersionNumber >= 4:
|
elif addressVersionNumber >= 4:
|
||||||
# If the user just clicked 'send' then the tag
|
# If the user just clicked 'send' then the tag
|
||||||
# (and other information) will already be in the
|
# (and other information) will already be in the
|
||||||
|
@ -1416,10 +1417,10 @@ class singleWorker(StoppableThread):
|
||||||
encodeVarint(addressVersionNumber)
|
encodeVarint(addressVersionNumber)
|
||||||
+ encodeVarint(streamNumber) + ripe
|
+ encodeVarint(streamNumber) + ripe
|
||||||
).digest()).digest()[32:]
|
).digest()).digest()[32:]
|
||||||
if tag not in self.state.neededPubkeys:
|
if tag not in state.neededPubkeys:
|
||||||
# We'll need this for when we receive a pubkey reply:
|
# We'll need this for when we receive a pubkey reply:
|
||||||
# it will be encrypted and we'll need to decrypt it.
|
# it will be encrypted and we'll need to decrypt it.
|
||||||
self.state.neededPubkeys[tag] = (
|
state.neededPubkeys[tag] = (
|
||||||
toAddress,
|
toAddress,
|
||||||
highlevelcrypto.makeCryptor(hexlify(privEncryptionKey))
|
highlevelcrypto.makeCryptor(hexlify(privEncryptionKey))
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,17 +4,18 @@ Network subsystem package
|
||||||
|
|
||||||
from announcethread import AnnounceThread
|
from announcethread import AnnounceThread
|
||||||
from connectionpool import BMConnectionPool
|
from connectionpool import BMConnectionPool
|
||||||
|
from node import Peer
|
||||||
from threads import StoppableThread
|
from threads import StoppableThread
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"AnnounceThread", "BMConnectionPool", "StoppableThread"
|
"AnnounceThread", "BMConnectionPool", "Peer", "StoppableThread"
|
||||||
# "AddrThread", "AnnounceThread", "BMNetworkThread", "Dandelion",
|
# "AddrThread", "AnnounceThread", "BMNetworkThread", "Dandelion",
|
||||||
# "DownloadThread", "InvThread", "ReceiveQueueThread", "UploadThread",
|
# "DownloadThread", "InvThread", "ReceiveQueueThread", "UploadThread",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def start(config, state):
|
def start(app):
|
||||||
"""Start network threads"""
|
"""Start network threads"""
|
||||||
from addrthread import AddrThread
|
from addrthread import AddrThread
|
||||||
from dandelion import Dandelion
|
from dandelion import Dandelion
|
||||||
|
@ -28,29 +29,31 @@ def start(config, state):
|
||||||
readKnownNodes()
|
readKnownNodes()
|
||||||
# init, needs to be early because other thread may access it early
|
# init, needs to be early because other thread may access it early
|
||||||
Dandelion()
|
Dandelion()
|
||||||
BMConnectionPool().connectToStream(1)
|
# init
|
||||||
asyncoreThread = BMNetworkThread(state)
|
pool = BMConnectionPool(app)
|
||||||
|
pool.connectToStream(1)
|
||||||
|
asyncoreThread = BMNetworkThread(pool)
|
||||||
asyncoreThread.daemon = True
|
asyncoreThread.daemon = True
|
||||||
asyncoreThread.start()
|
asyncoreThread.start()
|
||||||
invThread = InvThread(state)
|
invThread = InvThread(app)
|
||||||
invThread.daemon = True
|
invThread.daemon = True
|
||||||
invThread.start()
|
invThread.start()
|
||||||
addrThread = AddrThread(state)
|
addrThread = AddrThread(app)
|
||||||
addrThread.daemon = True
|
addrThread.daemon = True
|
||||||
addrThread.start()
|
addrThread.start()
|
||||||
downloadThread = DownloadThread()
|
downloadThread = DownloadThread(app)
|
||||||
downloadThread.daemon = True
|
downloadThread.daemon = True
|
||||||
downloadThread.start()
|
downloadThread.start()
|
||||||
uploadThread = UploadThread(state) # state is not used
|
uploadThread = UploadThread(app)
|
||||||
uploadThread.daemon = True
|
uploadThread.daemon = True
|
||||||
uploadThread.start()
|
uploadThread.start()
|
||||||
|
|
||||||
# Optional components
|
# Optional components
|
||||||
for i in range(config.getint('threads', 'receive')):
|
for i in range(app.config.getint('threads', 'receive')):
|
||||||
receiveQueueThread = ReceiveQueueThread(state, i)
|
receiveQueueThread = ReceiveQueueThread(app, i)
|
||||||
receiveQueueThread.daemon = True
|
receiveQueueThread.daemon = True
|
||||||
receiveQueueThread.start()
|
receiveQueueThread.start()
|
||||||
if config.safeGetBoolean('bitmessagesettings', 'udp'):
|
if app.config.safeGetBoolean('bitmessagesettings', 'udp'):
|
||||||
state.announceThread = AnnounceThread(state)
|
app.state.announceThread = AnnounceThread(app)
|
||||||
state.announceThread.daemon = True
|
app.state.announceThread.daemon = True
|
||||||
state.announceThread.start()
|
app.state.announceThread.start()
|
||||||
|
|
|
@ -5,13 +5,12 @@ from six.moves import queue
|
||||||
|
|
||||||
|
|
||||||
from helper_random import randomshuffle
|
from helper_random import randomshuffle
|
||||||
from network.assemble import assemble_addr
|
|
||||||
from network.connectionpool import BMConnectionPool
|
from network.connectionpool import BMConnectionPool
|
||||||
from queues import addrQueue
|
from queues import addrQueue
|
||||||
from threads import StoppableThread
|
from threads import StatefulThread
|
||||||
|
|
||||||
|
|
||||||
class AddrThread(StoppableThread):
|
class AddrThread(StatefulThread):
|
||||||
"""(Node) address broadcasting thread"""
|
"""(Node) address broadcasting thread"""
|
||||||
name = "AddrBroadcaster"
|
name = "AddrBroadcaster"
|
||||||
|
|
||||||
|
@ -40,7 +39,8 @@ class AddrThread(StoppableThread):
|
||||||
continue
|
continue
|
||||||
filtered.append((stream, peer, seen))
|
filtered.append((stream, peer, seen))
|
||||||
if filtered:
|
if filtered:
|
||||||
i.append_write_buf(assemble_addr(filtered))
|
i.append_write_buf(
|
||||||
|
self.protocol.assembleAddrMessage(filtered))
|
||||||
|
|
||||||
addrQueue.iterate()
|
addrQueue.iterate()
|
||||||
for i in range(len(chunk)):
|
for i in range(len(chunk)):
|
||||||
|
|
|
@ -3,14 +3,12 @@ Announce myself (node address)
|
||||||
"""
|
"""
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from bmconfigparser import BMConfigParser
|
from connectionpool import BMConnectionPool
|
||||||
from network.assemble import assemble_addr
|
|
||||||
from network.connectionpool import BMConnectionPool
|
|
||||||
from node import Peer
|
from node import Peer
|
||||||
from threads import StoppableThread
|
from threads import StatefulThread
|
||||||
|
|
||||||
|
|
||||||
class AnnounceThread(StoppableThread):
|
class AnnounceThread(StatefulThread):
|
||||||
"""A thread to manage regular announcing of this node"""
|
"""A thread to manage regular announcing of this node"""
|
||||||
name = "Announcer"
|
name = "Announcer"
|
||||||
announceInterval = 60
|
announceInterval = 60
|
||||||
|
@ -31,11 +29,9 @@ class AnnounceThread(StoppableThread):
|
||||||
if not connection.announcing:
|
if not connection.announcing:
|
||||||
continue
|
continue
|
||||||
for stream in self.state.streamsInWhichIAmParticipating:
|
for stream in self.state.streamsInWhichIAmParticipating:
|
||||||
addr = (
|
addr = (stream, Peer(
|
||||||
stream,
|
|
||||||
Peer(
|
|
||||||
'127.0.0.1',
|
'127.0.0.1',
|
||||||
BMConfigParser().safeGetInt(
|
self.config.safeGetInt('bitmessagesettings', 'port')),
|
||||||
'bitmessagesettings', 'port')),
|
|
||||||
time.time())
|
time.time())
|
||||||
connection.append_write_buf(assemble_addr([addr]))
|
connection.append_write_buf(
|
||||||
|
self.protocol.assembleAddrMessage([addr]))
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
"""
|
|
||||||
Create bitmessage protocol command packets
|
|
||||||
"""
|
|
||||||
import struct
|
|
||||||
|
|
||||||
import addresses
|
|
||||||
from network.constants import MAX_ADDR_COUNT
|
|
||||||
from network.node import Peer
|
|
||||||
from protocol import CreatePacket, encodeHost
|
|
||||||
|
|
||||||
|
|
||||||
def assemble_addr(peerList):
|
|
||||||
"""Create address command"""
|
|
||||||
if isinstance(peerList, Peer):
|
|
||||||
peerList = [peerList]
|
|
||||||
if not peerList:
|
|
||||||
return b''
|
|
||||||
retval = b''
|
|
||||||
for i in range(0, len(peerList), MAX_ADDR_COUNT):
|
|
||||||
payload = addresses.encodeVarint(len(peerList[i:i + MAX_ADDR_COUNT]))
|
|
||||||
for stream, peer, timestamp in peerList[i:i + MAX_ADDR_COUNT]:
|
|
||||||
# 64-bit time
|
|
||||||
payload += struct.pack('>Q', timestamp)
|
|
||||||
payload += struct.pack('>I', stream)
|
|
||||||
# service bit flags offered by this node
|
|
||||||
payload += struct.pack('>q', 1)
|
|
||||||
payload += encodeHost(peer.host)
|
|
||||||
# remote port
|
|
||||||
payload += struct.pack('>H', peer.port)
|
|
||||||
retval += CreatePacket('addr', payload)
|
|
||||||
return retval
|
|
|
@ -1,77 +0,0 @@
|
||||||
"""
|
|
||||||
Select which node to connect to
|
|
||||||
"""
|
|
||||||
# pylint: disable=too-many-branches
|
|
||||||
import logging
|
|
||||||
import random # nosec
|
|
||||||
|
|
||||||
import knownnodes
|
|
||||||
import protocol
|
|
||||||
import state
|
|
||||||
from bmconfigparser import BMConfigParser
|
|
||||||
from queues import queue, portCheckerQueue
|
|
||||||
|
|
||||||
logger = logging.getLogger('default')
|
|
||||||
|
|
||||||
|
|
||||||
def getDiscoveredPeer():
|
|
||||||
"""Get a peer from the local peer discovery list"""
|
|
||||||
try:
|
|
||||||
peer = random.choice(state.discoveredPeers.keys())
|
|
||||||
except (IndexError, KeyError):
|
|
||||||
raise ValueError
|
|
||||||
try:
|
|
||||||
del state.discoveredPeers[peer]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
return peer
|
|
||||||
|
|
||||||
|
|
||||||
def chooseConnection(stream):
|
|
||||||
"""Returns an appropriate connection"""
|
|
||||||
haveOnion = BMConfigParser().safeGet(
|
|
||||||
"bitmessagesettings", "socksproxytype")[0:5] == 'SOCKS'
|
|
||||||
onionOnly = BMConfigParser().safeGetBoolean(
|
|
||||||
"bitmessagesettings", "onionservicesonly")
|
|
||||||
try:
|
|
||||||
retval = portCheckerQueue.get(False)
|
|
||||||
portCheckerQueue.task_done()
|
|
||||||
return retval
|
|
||||||
except queue.Empty:
|
|
||||||
pass
|
|
||||||
# with a probability of 0.5, connect to a discovered peer
|
|
||||||
if random.choice((False, True)) and not haveOnion:
|
|
||||||
# discovered peers are already filtered by allowed streams
|
|
||||||
return getDiscoveredPeer()
|
|
||||||
for _ in range(50):
|
|
||||||
peer = random.choice(knownnodes.knownNodes[stream].keys())
|
|
||||||
try:
|
|
||||||
peer_info = knownnodes.knownNodes[stream][peer]
|
|
||||||
if peer_info.get('self'):
|
|
||||||
continue
|
|
||||||
rating = peer_info["rating"]
|
|
||||||
except TypeError:
|
|
||||||
logger.warning('Error in %s', peer)
|
|
||||||
rating = 0
|
|
||||||
if haveOnion:
|
|
||||||
# do not connect to raw IP addresses
|
|
||||||
# --keep all traffic within Tor overlay
|
|
||||||
if onionOnly and not peer.host.endswith('.onion'):
|
|
||||||
continue
|
|
||||||
# onion addresses have a higher priority when SOCKS
|
|
||||||
if peer.host.endswith('.onion') and rating > 0:
|
|
||||||
rating = 1
|
|
||||||
# TODO: need better check
|
|
||||||
elif not peer.host.startswith('bootstrap'):
|
|
||||||
encodedAddr = protocol.encodeHost(peer.host)
|
|
||||||
# don't connect to local IPs when using SOCKS
|
|
||||||
if not protocol.checkIPAddress(encodedAddr, False):
|
|
||||||
continue
|
|
||||||
if rating > 1:
|
|
||||||
rating = 1
|
|
||||||
try:
|
|
||||||
if 0.05 / (1.0 - rating) > random.random():
|
|
||||||
return peer
|
|
||||||
except ZeroDivisionError:
|
|
||||||
return peer
|
|
||||||
raise ValueError
|
|
|
@ -3,29 +3,91 @@
|
||||||
"""
|
"""
|
||||||
import errno
|
import errno
|
||||||
import logging
|
import logging
|
||||||
|
import random # noseq
|
||||||
import re
|
import re
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import asyncore_pollchoose as asyncore
|
# magic imports
|
||||||
import helper_random
|
import helper_random
|
||||||
|
from queues import queue, portCheckerQueue
|
||||||
|
from singleton import Singleton
|
||||||
|
|
||||||
|
import asyncore_pollchoose as asyncore
|
||||||
import knownnodes
|
import knownnodes
|
||||||
import protocol
|
from advanceddispatcher import AdvancedDispatcher
|
||||||
import state
|
|
||||||
from bmconfigparser import BMConfigParser
|
|
||||||
from connectionchooser import chooseConnection
|
|
||||||
from node import Peer
|
from node import Peer
|
||||||
from proxy import Proxy
|
from proxy import Proxy
|
||||||
from singleton import Singleton
|
|
||||||
from tcp import (
|
from tcp import (
|
||||||
bootstrap, Socks4aBMConnection, Socks5BMConnection,
|
bootstrap, Socks4aBMConnection, Socks5BMConnection, TCPConnection)
|
||||||
TCPConnection, TCPServer)
|
|
||||||
from udp import UDPSocket
|
from udp import UDPSocket
|
||||||
|
|
||||||
logger = logging.getLogger('default')
|
logger = logging.getLogger('default')
|
||||||
|
|
||||||
|
|
||||||
|
class TCPServer(AdvancedDispatcher):
|
||||||
|
"""TCP connection server for Bitmessage protocol"""
|
||||||
|
|
||||||
|
def __init__(self, pool, host='127.0.0.1', port=8444):
|
||||||
|
self.pool = pool
|
||||||
|
if not hasattr(self, '_map'):
|
||||||
|
AdvancedDispatcher.__init__(self)
|
||||||
|
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
self.set_reuse_addr()
|
||||||
|
for attempt in range(50):
|
||||||
|
try:
|
||||||
|
if attempt > 0:
|
||||||
|
logger.warning('Failed to bind on port %s', port)
|
||||||
|
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:
|
||||||
|
logger.warning('Setting port to %s', port)
|
||||||
|
self.pool.config.set(
|
||||||
|
'bitmessagesettings', 'port', str(port))
|
||||||
|
self.pool.config.save()
|
||||||
|
break
|
||||||
|
self.destination = Peer(host, port)
|
||||||
|
self.bound = True
|
||||||
|
self.listen(5)
|
||||||
|
|
||||||
|
def is_bound(self):
|
||||||
|
"""Is the socket bound?"""
|
||||||
|
try:
|
||||||
|
return self.bound
|
||||||
|
except AttributeError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def handle_accept(self):
|
||||||
|
"""Incoming connection callback"""
|
||||||
|
try:
|
||||||
|
sock = self.accept()[0]
|
||||||
|
except (TypeError, IndexError):
|
||||||
|
return
|
||||||
|
|
||||||
|
self.pool.state.ownAddresses[Peer(*sock.getsockname())] = True
|
||||||
|
if (
|
||||||
|
len(self.pool)
|
||||||
|
> self.pool.config.safeGetInt(
|
||||||
|
'bitmessagesettings', 'maxtotalconnections')
|
||||||
|
+ self.pool.config.safeGetInt(
|
||||||
|
'bitmessagesettings', 'maxbootstrapconnections') + 10
|
||||||
|
):
|
||||||
|
# 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:
|
||||||
|
self.pool.addConnection(TCPConnection(self.pool, sock=sock))
|
||||||
|
except socket.error:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class BMConnectionPool(object):
|
class BMConnectionPool(object):
|
||||||
"""Pool of all existing connections"""
|
"""Pool of all existing connections"""
|
||||||
|
@ -44,11 +106,16 @@ class BMConnectionPool(object):
|
||||||
without compromising security.
|
without compromising security.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, app=None):
|
||||||
|
if not app:
|
||||||
|
return # for singleton convenience
|
||||||
|
self.config = app.config
|
||||||
|
self.protocol = app.protocol
|
||||||
|
self.state = app.state
|
||||||
asyncore.set_rates(
|
asyncore.set_rates(
|
||||||
BMConfigParser().safeGetInt(
|
app.config.safeGetInt(
|
||||||
"bitmessagesettings", "maxdownloadrate"),
|
"bitmessagesettings", "maxdownloadrate"),
|
||||||
BMConfigParser().safeGetInt(
|
app.config.safeGetInt(
|
||||||
"bitmessagesettings", "maxuploadrate")
|
"bitmessagesettings", "maxuploadrate")
|
||||||
)
|
)
|
||||||
self.outboundConnections = {}
|
self.outboundConnections = {}
|
||||||
|
@ -60,7 +127,7 @@ class BMConnectionPool(object):
|
||||||
self._spawnWait = 2
|
self._spawnWait = 2
|
||||||
self._bootstrapped = False
|
self._bootstrapped = False
|
||||||
|
|
||||||
trustedPeer = BMConfigParser().safeGet(
|
trustedPeer = app.config.safeGet(
|
||||||
'bitmessagesettings', 'trustedpeer')
|
'bitmessagesettings', 'trustedpeer')
|
||||||
try:
|
try:
|
||||||
if trustedPeer:
|
if trustedPeer:
|
||||||
|
@ -90,7 +157,7 @@ class BMConnectionPool(object):
|
||||||
def connectToStream(self, streamNumber):
|
def connectToStream(self, streamNumber):
|
||||||
"""Connect to a bitmessage stream"""
|
"""Connect to a bitmessage stream"""
|
||||||
self.streams.append(streamNumber)
|
self.streams.append(streamNumber)
|
||||||
state.streamsInWhichIAmParticipating.append(streamNumber)
|
self.state.streamsInWhichIAmParticipating.append(streamNumber)
|
||||||
|
|
||||||
def getConnectionByAddr(self, addr):
|
def getConnectionByAddr(self, addr):
|
||||||
"""
|
"""
|
||||||
|
@ -160,32 +227,30 @@ class BMConnectionPool(object):
|
||||||
pass
|
pass
|
||||||
connection.handle_close()
|
connection.handle_close()
|
||||||
|
|
||||||
@staticmethod
|
def getListeningIP(self):
|
||||||
def getListeningIP():
|
|
||||||
"""What IP are we supposed to be listening on?"""
|
"""What IP are we supposed to be listening on?"""
|
||||||
if BMConfigParser().safeGet(
|
if self.config.safeGet(
|
||||||
"bitmessagesettings", "onionhostname").endswith(".onion"):
|
"bitmessagesettings", "onionhostname").endswith(".onion"):
|
||||||
host = BMConfigParser().safeGet(
|
host = self.config.safeGet("bitmessagesettings", "onionbindip")
|
||||||
"bitmessagesettings", "onionbindip")
|
|
||||||
else:
|
else:
|
||||||
host = '127.0.0.1'
|
host = '127.0.0.1'
|
||||||
if (
|
if (
|
||||||
BMConfigParser().safeGetBoolean("bitmessagesettings", "sockslisten")
|
self.config.safeGetBoolean("bitmessagesettings", "sockslisten")
|
||||||
or BMConfigParser().safeGet("bitmessagesettings", "socksproxytype")
|
or self.config.safeGet("bitmessagesettings", "socksproxytype")
|
||||||
== "none"
|
== "none"
|
||||||
):
|
):
|
||||||
# python doesn't like bind + INADDR_ANY?
|
# python doesn't like bind + INADDR_ANY?
|
||||||
# host = socket.INADDR_ANY
|
# host = socket.INADDR_ANY
|
||||||
host = BMConfigParser().get("network", "bind")
|
host = self.config.get("network", "bind")
|
||||||
return host
|
return host
|
||||||
|
|
||||||
def startListening(self, bind=None):
|
def startListening(self, bind=None):
|
||||||
"""Open a listening socket and start accepting connections on it"""
|
"""Open a listening socket and start accepting connections on it"""
|
||||||
if bind is None:
|
if bind is None:
|
||||||
bind = self.getListeningIP()
|
bind = self.getListeningIP()
|
||||||
port = BMConfigParser().safeGetInt("bitmessagesettings", "port")
|
port = self.config.safeGetInt("bitmessagesettings", "port")
|
||||||
# correct port even if it changed
|
# correct port even if it changed
|
||||||
ls = TCPServer(host=bind, port=port)
|
ls = TCPServer(self, host=bind, port=port)
|
||||||
self.listeningSockets[ls.destination] = ls
|
self.listeningSockets[ls.destination] = ls
|
||||||
|
|
||||||
def startUDPSocket(self, bind=None):
|
def startUDPSocket(self, bind=None):
|
||||||
|
@ -195,17 +260,17 @@ class BMConnectionPool(object):
|
||||||
"""
|
"""
|
||||||
if bind is None:
|
if bind is None:
|
||||||
host = self.getListeningIP()
|
host = self.getListeningIP()
|
||||||
udpSocket = UDPSocket(host=host, announcing=True)
|
udpSocket = UDPSocket(self, host=host, announcing=True)
|
||||||
else:
|
else:
|
||||||
if bind is False:
|
if bind is False:
|
||||||
udpSocket = UDPSocket(announcing=False)
|
udpSocket = UDPSocket(self, announcing=False)
|
||||||
else:
|
else:
|
||||||
udpSocket = UDPSocket(host=bind, announcing=True)
|
udpSocket = UDPSocket(self, host=bind, announcing=True)
|
||||||
self.udpSockets[udpSocket.listening.host] = udpSocket
|
self.udpSockets[udpSocket.listening.host] = udpSocket
|
||||||
|
|
||||||
def startBootstrappers(self):
|
def startBootstrappers(self):
|
||||||
"""Run the process of resolving bootstrap hostnames"""
|
"""Run the process of resolving bootstrap hostnames"""
|
||||||
proxy_type = BMConfigParser().safeGet(
|
proxy_type = self.config.safeGet(
|
||||||
'bitmessagesettings', 'socksproxytype')
|
'bitmessagesettings', 'socksproxytype')
|
||||||
# A plugins may be added here
|
# A plugins may be added here
|
||||||
hostname = None
|
hostname = None
|
||||||
|
@ -229,7 +294,68 @@ class BMConnectionPool(object):
|
||||||
hostname = 'bootstrap%s.bitmessage.org' % port
|
hostname = 'bootstrap%s.bitmessage.org' % port
|
||||||
else:
|
else:
|
||||||
port = 8444
|
port = 8444
|
||||||
self.addConnection(bootstrapper(hostname, port))
|
self.addConnection(bootstrapper(self, hostname, port))
|
||||||
|
|
||||||
|
def chooseConnection(self, stream):
|
||||||
|
"""Returns an appropriate connection"""
|
||||||
|
def getDiscoveredPeer():
|
||||||
|
"""Get a peer from the local peer discovery list"""
|
||||||
|
try:
|
||||||
|
peer = random.choice(self.state.discoveredPeers.keys())
|
||||||
|
except (IndexError, KeyError):
|
||||||
|
raise ValueError
|
||||||
|
try:
|
||||||
|
del self.state.discoveredPeers[peer]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return peer
|
||||||
|
|
||||||
|
haveOnion = self.config.safeGet(
|
||||||
|
"bitmessagesettings", "socksproxytype")[0:5] == 'SOCKS'
|
||||||
|
onionOnly = self.config.safeGetBoolean(
|
||||||
|
"bitmessagesettings", "onionservicesonly")
|
||||||
|
try:
|
||||||
|
retval = portCheckerQueue.get(False)
|
||||||
|
portCheckerQueue.task_done()
|
||||||
|
return retval
|
||||||
|
except queue.Empty:
|
||||||
|
pass
|
||||||
|
# with a probability of 0.5, connect to a discovered peer
|
||||||
|
if random.choice((False, True)) and not haveOnion:
|
||||||
|
# discovered peers are already filtered by allowed streams
|
||||||
|
return getDiscoveredPeer()
|
||||||
|
for _ in range(50):
|
||||||
|
peer = random.choice(knownnodes.knownNodes[stream].keys())
|
||||||
|
try:
|
||||||
|
peer_info = knownnodes.knownNodes[stream][peer]
|
||||||
|
if peer_info.get('self'):
|
||||||
|
continue
|
||||||
|
rating = peer_info["rating"]
|
||||||
|
except TypeError:
|
||||||
|
logger.warning('Error in %s', peer)
|
||||||
|
rating = 0
|
||||||
|
if haveOnion:
|
||||||
|
# do not connect to raw IP addresses
|
||||||
|
# --keep all traffic within Tor overlay
|
||||||
|
if onionOnly and not peer.host.endswith('.onion'):
|
||||||
|
continue
|
||||||
|
# onion addresses have a higher priority when SOCKS
|
||||||
|
if peer.host.endswith('.onion') and rating > 0:
|
||||||
|
rating = 1
|
||||||
|
# TODO: need better check
|
||||||
|
elif not peer.host.startswith('bootstrap'):
|
||||||
|
encodedAddr = self.protocol.encodeHost(peer.host)
|
||||||
|
# don't connect to local IPs when using SOCKS
|
||||||
|
if not self.protocol.checkIPAddress(encodedAddr, False):
|
||||||
|
continue
|
||||||
|
if rating > 1:
|
||||||
|
rating = 1
|
||||||
|
try:
|
||||||
|
if 0.05 / (1.0 - rating) > random.random():
|
||||||
|
return peer
|
||||||
|
except ZeroDivisionError:
|
||||||
|
return peer
|
||||||
|
raise ValueError
|
||||||
|
|
||||||
def loop(self): # pylint: disable=too-many-branches,too-many-statements
|
def loop(self): # pylint: disable=too-many-branches,too-many-statements
|
||||||
"""Main Connectionpool's loop"""
|
"""Main Connectionpool's loop"""
|
||||||
|
@ -237,21 +363,20 @@ class BMConnectionPool(object):
|
||||||
# defaults to empty loop if outbound connections are maxed
|
# defaults to empty loop if outbound connections are maxed
|
||||||
spawnConnections = False
|
spawnConnections = False
|
||||||
acceptConnections = True
|
acceptConnections = True
|
||||||
if BMConfigParser().safeGetBoolean(
|
if self.config.safeGetBoolean('bitmessagesettings', 'dontconnect'):
|
||||||
'bitmessagesettings', 'dontconnect'):
|
|
||||||
acceptConnections = False
|
acceptConnections = False
|
||||||
elif BMConfigParser().safeGetBoolean(
|
elif self.config.safeGetBoolean(
|
||||||
'bitmessagesettings', 'sendoutgoingconnections'):
|
'bitmessagesettings', 'sendoutgoingconnections'):
|
||||||
spawnConnections = True
|
spawnConnections = True
|
||||||
socksproxytype = BMConfigParser().safeGet(
|
socksproxytype = self.config.safeGet(
|
||||||
'bitmessagesettings', 'socksproxytype', '')
|
'bitmessagesettings', 'socksproxytype', '')
|
||||||
onionsocksproxytype = BMConfigParser().safeGet(
|
onionsocksproxytype = self.config.safeGet(
|
||||||
'bitmessagesettings', 'onionsocksproxytype', '')
|
'bitmessagesettings', 'onionsocksproxytype', '')
|
||||||
if (
|
if (
|
||||||
socksproxytype[:5] == 'SOCKS'
|
socksproxytype[:5] == 'SOCKS'
|
||||||
and not BMConfigParser().safeGetBoolean(
|
and not self.config.safeGetBoolean(
|
||||||
'bitmessagesettings', 'sockslisten')
|
'bitmessagesettings', 'sockslisten')
|
||||||
and '.onion' not in BMConfigParser().safeGet(
|
and '.onion' not in self.config.safeGet(
|
||||||
'bitmessagesettings', 'onionhostname', '')
|
'bitmessagesettings', 'onionhostname', '')
|
||||||
):
|
):
|
||||||
acceptConnections = False
|
acceptConnections = False
|
||||||
|
@ -264,9 +389,9 @@ class BMConnectionPool(object):
|
||||||
if not self._bootstrapped:
|
if not self._bootstrapped:
|
||||||
self._bootstrapped = True
|
self._bootstrapped = True
|
||||||
Proxy.proxy = (
|
Proxy.proxy = (
|
||||||
BMConfigParser().safeGet(
|
self.config.safeGet(
|
||||||
'bitmessagesettings', 'sockshostname'),
|
'bitmessagesettings', 'sockshostname'),
|
||||||
BMConfigParser().safeGetInt(
|
self.config.safeGetInt(
|
||||||
'bitmessagesettings', 'socksport')
|
'bitmessagesettings', 'socksport')
|
||||||
)
|
)
|
||||||
# TODO AUTH
|
# TODO AUTH
|
||||||
|
@ -275,9 +400,9 @@ class BMConnectionPool(object):
|
||||||
if not onionsocksproxytype.startswith("SOCKS"):
|
if not onionsocksproxytype.startswith("SOCKS"):
|
||||||
raise ValueError
|
raise ValueError
|
||||||
Proxy.onion_proxy = (
|
Proxy.onion_proxy = (
|
||||||
BMConfigParser().safeGet(
|
self.config.safeGet(
|
||||||
'network', 'onionsockshostname', None),
|
'network', 'onionsockshostname', None),
|
||||||
BMConfigParser().safeGet(
|
self.config.safeGet(
|
||||||
'network', 'onionsocksport', None)
|
'network', 'onionsocksport', None)
|
||||||
)
|
)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
@ -286,12 +411,13 @@ class BMConnectionPool(object):
|
||||||
1 for c in self.outboundConnections.values()
|
1 for c in self.outboundConnections.values()
|
||||||
if (c.connected and c.fullyEstablished))
|
if (c.connected and c.fullyEstablished))
|
||||||
pending = len(self.outboundConnections) - established
|
pending = len(self.outboundConnections) - established
|
||||||
if established < BMConfigParser().safeGetInt(
|
if established < self.config.safeGetInt(
|
||||||
'bitmessagesettings', 'maxoutboundconnections'):
|
'bitmessagesettings', 'maxoutboundconnections'):
|
||||||
for i in range(
|
for i in range(
|
||||||
state.maximumNumberOfHalfOpenConnections - pending):
|
self.state.maximumNumberOfHalfOpenConnections - pending
|
||||||
|
):
|
||||||
try:
|
try:
|
||||||
chosen = self.trustedPeer or chooseConnection(
|
chosen = self.trustedPeer or self.chooseConnection(
|
||||||
helper_random.randomchoice(self.streams))
|
helper_random.randomchoice(self.streams))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
continue
|
continue
|
||||||
|
@ -300,11 +426,11 @@ class BMConnectionPool(object):
|
||||||
if chosen.host in self.inboundConnections:
|
if chosen.host in self.inboundConnections:
|
||||||
continue
|
continue
|
||||||
# don't connect to self
|
# don't connect to self
|
||||||
if chosen in state.ownAddresses:
|
if chosen in self.state.ownAddresses:
|
||||||
continue
|
continue
|
||||||
# don't connect to the hosts from the same
|
# don't connect to the hosts from the same
|
||||||
# network group, defense against sibyl attacks
|
# network group, defense against sibyl attacks
|
||||||
host_network_group = protocol.network_group(
|
host_network_group = self.protocol.network_group(
|
||||||
chosen.host)
|
chosen.host)
|
||||||
same_group = False
|
same_group = False
|
||||||
for j in self.outboundConnections.values():
|
for j in self.outboundConnections.values():
|
||||||
|
@ -319,15 +445,19 @@ class BMConnectionPool(object):
|
||||||
try:
|
try:
|
||||||
if chosen.host.endswith(".onion") and Proxy.onion_proxy:
|
if chosen.host.endswith(".onion") and Proxy.onion_proxy:
|
||||||
if onionsocksproxytype == "SOCKS5":
|
if onionsocksproxytype == "SOCKS5":
|
||||||
self.addConnection(Socks5BMConnection(chosen))
|
self.addConnection(
|
||||||
|
Socks5BMConnection(self, chosen))
|
||||||
elif onionsocksproxytype == "SOCKS4a":
|
elif onionsocksproxytype == "SOCKS4a":
|
||||||
self.addConnection(Socks4aBMConnection(chosen))
|
self.addConnection(
|
||||||
|
Socks4aBMConnection(self, chosen))
|
||||||
elif socksproxytype == "SOCKS5":
|
elif socksproxytype == "SOCKS5":
|
||||||
self.addConnection(Socks5BMConnection(chosen))
|
self.addConnection(
|
||||||
|
Socks5BMConnection(self, chosen))
|
||||||
elif socksproxytype == "SOCKS4a":
|
elif socksproxytype == "SOCKS4a":
|
||||||
self.addConnection(Socks4aBMConnection(chosen))
|
self.addConnection(
|
||||||
|
Socks4aBMConnection(self, chosen))
|
||||||
else:
|
else:
|
||||||
self.addConnection(TCPConnection(chosen))
|
self.addConnection(TCPConnection(self, chosen))
|
||||||
except socket.error as e:
|
except socket.error as e:
|
||||||
if e.errno == errno.ENETUNREACH:
|
if e.errno == errno.ENETUNREACH:
|
||||||
continue
|
continue
|
||||||
|
@ -340,22 +470,21 @@ class BMConnectionPool(object):
|
||||||
|
|
||||||
if acceptConnections:
|
if acceptConnections:
|
||||||
if not self.listeningSockets:
|
if not self.listeningSockets:
|
||||||
if BMConfigParser().safeGet('network', 'bind') == '':
|
if self.config.safeGet('network', 'bind') == '':
|
||||||
self.startListening()
|
self.startListening()
|
||||||
else:
|
else:
|
||||||
for bind in re.sub(
|
for bind in re.sub(
|
||||||
r'[^\w.]+', ' ',
|
r'[^\w.]+', ' ', self.config.safeGet('network', 'bind')
|
||||||
BMConfigParser().safeGet('network', 'bind')
|
|
||||||
).split():
|
).split():
|
||||||
self.startListening(bind)
|
self.startListening(bind)
|
||||||
logger.info('Listening for incoming connections.')
|
logger.info('Listening for incoming connections.')
|
||||||
if not self.udpSockets:
|
if not self.udpSockets:
|
||||||
if BMConfigParser().safeGet('network', 'bind') == '':
|
if self.config.safeGet('network', 'bind') == '':
|
||||||
self.startUDPSocket()
|
self.startUDPSocket()
|
||||||
else:
|
else:
|
||||||
for bind in re.sub(
|
for bind in re.sub(
|
||||||
r'[^\w.]+', ' ',
|
r'[^\w.]+', ' ',
|
||||||
BMConfigParser().safeGet('network', 'bind')
|
self.config.safeGet('network', 'bind')
|
||||||
).split():
|
).split():
|
||||||
self.startUDPSocket(bind)
|
self.startUDPSocket(bind)
|
||||||
self.startUDPSocket(False)
|
self.startUDPSocket(False)
|
||||||
|
@ -384,7 +513,7 @@ class BMConnectionPool(object):
|
||||||
minTx -= 300 - 20
|
minTx -= 300 - 20
|
||||||
if i.lastTx < minTx:
|
if i.lastTx < minTx:
|
||||||
if i.fullyEstablished:
|
if i.fullyEstablished:
|
||||||
i.append_write_buf(protocol.CreatePacket('ping'))
|
i.append_write_buf(self.protocol.CreatePacket('ping'))
|
||||||
else:
|
else:
|
||||||
i.close_reason = "Timeout (%is)" % (
|
i.close_reason = "Timeout (%is)" % (
|
||||||
time.time() - i.lastTx)
|
time.time() - i.lastTx)
|
||||||
|
|
|
@ -5,8 +5,6 @@ Network protocol constants
|
||||||
|
|
||||||
#: address is online if online less than this many seconds ago
|
#: address is online if online less than this many seconds ago
|
||||||
ADDRESS_ALIVE = 10800
|
ADDRESS_ALIVE = 10800
|
||||||
#: protocol specification says max 1000 addresses in one addr command
|
|
||||||
MAX_ADDR_COUNT = 1000
|
|
||||||
#: ~1.6 MB which is the maximum possible size of an inv message.
|
#: ~1.6 MB which is the maximum possible size of an inv message.
|
||||||
MAX_MESSAGE_SIZE = 1600100
|
MAX_MESSAGE_SIZE = 1600100
|
||||||
#: 2**18 = 256kB is the maximum size of an object payload
|
#: 2**18 = 256kB is the maximum size of an object payload
|
||||||
|
|
|
@ -3,17 +3,17 @@
|
||||||
"""
|
"""
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import addresses
|
|
||||||
import helper_random
|
import helper_random
|
||||||
import protocol
|
from inventory import Inventory # magic import
|
||||||
|
|
||||||
from dandelion import Dandelion
|
from dandelion import Dandelion
|
||||||
from inventory import Inventory
|
# TODO: from connectionpool import
|
||||||
from network.connectionpool import BMConnectionPool
|
from network.connectionpool import BMConnectionPool
|
||||||
from objectracker import missingObjects
|
from objectracker import missingObjects
|
||||||
from threads import StoppableThread
|
from threads import StatefulThread
|
||||||
|
|
||||||
|
|
||||||
class DownloadThread(StoppableThread):
|
class DownloadThread(StatefulThread):
|
||||||
"""Thread-based class for downloading from connections"""
|
"""Thread-based class for downloading from connections"""
|
||||||
minPending = 200
|
minPending = 200
|
||||||
maxRequestChunk = 1000
|
maxRequestChunk = 1000
|
||||||
|
@ -21,8 +21,8 @@ class DownloadThread(StoppableThread):
|
||||||
cleanInterval = 60
|
cleanInterval = 60
|
||||||
requestExpires = 3600
|
requestExpires = 3600
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, app):
|
||||||
super(DownloadThread, self).__init__(None, name="Downloader")
|
super(DownloadThread, self).__init__(app, name="Downloader")
|
||||||
self.lastCleaned = time.time()
|
self.lastCleaned = time.time()
|
||||||
|
|
||||||
def cleanPending(self):
|
def cleanPending(self):
|
||||||
|
@ -72,8 +72,9 @@ class DownloadThread(StoppableThread):
|
||||||
missingObjects[chunk] = now
|
missingObjects[chunk] = now
|
||||||
if not chunkCount:
|
if not chunkCount:
|
||||||
continue
|
continue
|
||||||
payload[0:0] = addresses.encodeVarint(chunkCount)
|
payload[0:0] = self.protocol.encodeVarint(chunkCount)
|
||||||
i.append_write_buf(protocol.CreatePacket('getdata', payload))
|
i.append_write_buf(
|
||||||
|
self.protocol.CreatePacket('getdata', payload))
|
||||||
self.logger.debug(
|
self.logger.debug(
|
||||||
'%s:%i Requesting %i objects',
|
'%s:%i Requesting %i objects',
|
||||||
i.destination.host, i.destination.port, chunkCount)
|
i.destination.host, i.destination.port, chunkCount)
|
||||||
|
|
|
@ -5,12 +5,10 @@ import Queue
|
||||||
import random
|
import random
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
import addresses
|
|
||||||
import protocol
|
|
||||||
from network.connectionpool import BMConnectionPool
|
from network.connectionpool import BMConnectionPool
|
||||||
from network.dandelion import Dandelion
|
from network.dandelion import Dandelion
|
||||||
from queues import invQueue
|
from queues import invQueue # FIXME: get from app?
|
||||||
from threads import StoppableThread
|
from threads import StatefulThread
|
||||||
|
|
||||||
|
|
||||||
def handleExpiredDandelion(expired):
|
def handleExpiredDandelion(expired):
|
||||||
|
@ -31,7 +29,7 @@ def handleExpiredDandelion(expired):
|
||||||
i.objectsNewToThem[hashid] = time()
|
i.objectsNewToThem[hashid] = time()
|
||||||
|
|
||||||
|
|
||||||
class InvThread(StoppableThread):
|
class InvThread(StatefulThread):
|
||||||
"""Main thread that sends inv annoucements"""
|
"""Main thread that sends inv annoucements"""
|
||||||
|
|
||||||
name = "InvBroadcaster"
|
name = "InvBroadcaster"
|
||||||
|
@ -79,7 +77,9 @@ class InvThread(StoppableThread):
|
||||||
if random.randint(1, 100) >= self.state.dandelion:
|
if random.randint(1, 100) >= self.state.dandelion:
|
||||||
fluffs.append(inv[1])
|
fluffs.append(inv[1])
|
||||||
# send a dinv only if the stem node supports dandelion
|
# send a dinv only if the stem node supports dandelion
|
||||||
elif connection.services & protocol.NODE_DANDELION > 0:
|
elif (
|
||||||
|
connection.services
|
||||||
|
& self.protocol.NODE_DANDELION > 0):
|
||||||
stems.append(inv[1])
|
stems.append(inv[1])
|
||||||
else:
|
else:
|
||||||
fluffs.append(inv[1])
|
fluffs.append(inv[1])
|
||||||
|
@ -88,15 +88,15 @@ class InvThread(StoppableThread):
|
||||||
|
|
||||||
if fluffs:
|
if fluffs:
|
||||||
random.shuffle(fluffs)
|
random.shuffle(fluffs)
|
||||||
connection.append_write_buf(protocol.CreatePacket(
|
connection.append_write_buf(self.protocol.CreatePacket(
|
||||||
'inv',
|
'inv',
|
||||||
addresses.encodeVarint(
|
self.protocol.encodeVarint(
|
||||||
len(fluffs)) + ''.join(fluffs)))
|
len(fluffs)) + ''.join(fluffs)))
|
||||||
if stems:
|
if stems:
|
||||||
random.shuffle(stems)
|
random.shuffle(stems)
|
||||||
connection.append_write_buf(protocol.CreatePacket(
|
connection.append_write_buf(self.protocol.CreatePacket(
|
||||||
'dinv',
|
'dinv',
|
||||||
addresses.encodeVarint(
|
self.protocol.encodeVarint(
|
||||||
len(stems)) + ''.join(stems)))
|
len(stems)) + ''.join(stems)))
|
||||||
|
|
||||||
invQueue.iterate()
|
invQueue.iterate()
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
"""
|
"""
|
||||||
A thread to handle network concerns
|
A thread to handle network concerns
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from queues import excQueue # magic import
|
||||||
|
|
||||||
import network.asyncore_pollchoose as asyncore
|
import network.asyncore_pollchoose as asyncore
|
||||||
from network.connectionpool import BMConnectionPool
|
|
||||||
from queues import excQueue
|
|
||||||
from threads import StoppableThread
|
from threads import StoppableThread
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,27 +12,31 @@ class BMNetworkThread(StoppableThread):
|
||||||
"""Main network thread"""
|
"""Main network thread"""
|
||||||
name = "Asyncore"
|
name = "Asyncore"
|
||||||
|
|
||||||
|
def __init__(self, pool):
|
||||||
|
self.pool = pool
|
||||||
|
super(BMNetworkThread, self).__init__(self.name)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
try:
|
try:
|
||||||
while not self._stopped and self.state.shutdown == 0:
|
while not self._stopped and self.pool.state.shutdown == 0:
|
||||||
BMConnectionPool().loop()
|
self.pool.loop()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
excQueue.put((self.name, e))
|
excQueue.put((self.name, e))
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def stopThread(self):
|
def stopThread(self):
|
||||||
super(BMNetworkThread, self).stopThread()
|
super(BMNetworkThread, self).stopThread()
|
||||||
for i in BMConnectionPool().listeningSockets.values():
|
for i in self.pool.listeningSockets.values():
|
||||||
try:
|
try:
|
||||||
i.close()
|
i.close()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
for i in BMConnectionPool().outboundConnections.values():
|
for i in self.pool.outboundConnections.values():
|
||||||
try:
|
try:
|
||||||
i.close()
|
i.close()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
for i in BMConnectionPool().inboundConnections.values():
|
for i in self.pool.inboundConnections.values():
|
||||||
try:
|
try:
|
||||||
i.close()
|
i.close()
|
||||||
except:
|
except:
|
||||||
|
|
|
@ -8,15 +8,15 @@ import socket
|
||||||
from network.advanceddispatcher import UnknownStateError
|
from network.advanceddispatcher import UnknownStateError
|
||||||
from network.connectionpool import BMConnectionPool
|
from network.connectionpool import BMConnectionPool
|
||||||
from queues import receiveDataQueue
|
from queues import receiveDataQueue
|
||||||
from threads import StoppableThread
|
from threads import StatefulThread
|
||||||
|
|
||||||
|
|
||||||
class ReceiveQueueThread(StoppableThread):
|
class ReceiveQueueThread(StatefulThread):
|
||||||
"""This thread processes data received from the network
|
"""This thread processes data received from the network
|
||||||
(which is done by the asyncore thread)"""
|
(which is done by the asyncore thread)"""
|
||||||
def __init__(self, state, num=0):
|
def __init__(self, app, num=0):
|
||||||
super(ReceiveQueueThread, self).__init__(
|
super(ReceiveQueueThread, self).__init__(
|
||||||
state, name="ReceiveQueue_%i" % num)
|
app, name="ReceiveQueue_%i" % num)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
while not self._stopped and self.state.shutdown == 0:
|
while not self._stopped and self.state.shutdown == 0:
|
||||||
|
|
|
@ -5,22 +5,20 @@ TCP protocol handler
|
||||||
import l10n
|
import l10n
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
import random
|
|
||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import addresses
|
# magic imports
|
||||||
import asyncore_pollchoose as asyncore
|
|
||||||
import connectionpool
|
|
||||||
import helper_random
|
import helper_random
|
||||||
import knownnodes
|
|
||||||
import protocol
|
|
||||||
import state
|
|
||||||
from bmconfigparser import BMConfigParser
|
|
||||||
from helper_random import randomBytes
|
|
||||||
from inventory import Inventory
|
from inventory import Inventory
|
||||||
|
from queues import invQueue, receiveDataQueue, UISignalQueue
|
||||||
|
from tr import _translate
|
||||||
|
|
||||||
|
import asyncore_pollchoose as asyncore
|
||||||
|
|
||||||
|
import knownnodes
|
||||||
|
# TODO: not use network. here because this module is inside the network package
|
||||||
from network.advanceddispatcher import AdvancedDispatcher
|
from network.advanceddispatcher import AdvancedDispatcher
|
||||||
from network.assemble import assemble_addr
|
|
||||||
from network.bmproto import BMProto
|
from network.bmproto import BMProto
|
||||||
from network.constants import MAX_OBJECT_COUNT
|
from network.constants import MAX_OBJECT_COUNT
|
||||||
from network.dandelion import Dandelion
|
from network.dandelion import Dandelion
|
||||||
|
@ -29,8 +27,7 @@ from network.socks4a import Socks4aConnection
|
||||||
from network.socks5 import Socks5Connection
|
from network.socks5 import Socks5Connection
|
||||||
from network.tls import TLSDispatcher
|
from network.tls import TLSDispatcher
|
||||||
from node import Peer
|
from node import Peer
|
||||||
from queues import invQueue, receiveDataQueue, UISignalQueue
|
|
||||||
from tr import _translate
|
|
||||||
|
|
||||||
logger = logging.getLogger('default')
|
logger = logging.getLogger('default')
|
||||||
|
|
||||||
|
@ -45,8 +42,10 @@ class TCPConnection(BMProto, TLSDispatcher):
|
||||||
.. todo:: Look to understand and/or fix the non-parent-init-called
|
.. todo:: Look to understand and/or fix the non-parent-init-called
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, address=None, sock=None):
|
def __init__(self, pool, address=None, sock=None):
|
||||||
BMProto.__init__(self, address=address, sock=sock)
|
BMProto.__init__(self, address=address, sock=sock)
|
||||||
|
self.pool = pool
|
||||||
|
self.protocol = pool.protocol
|
||||||
self.verackReceived = False
|
self.verackReceived = False
|
||||||
self.verackSent = False
|
self.verackSent = False
|
||||||
self.streams = [0]
|
self.streams = [0]
|
||||||
|
@ -60,7 +59,7 @@ class TCPConnection(BMProto, TLSDispatcher):
|
||||||
logger.debug(
|
logger.debug(
|
||||||
'Received connection from %s:%i',
|
'Received connection from %s:%i',
|
||||||
self.destination.host, self.destination.port)
|
self.destination.host, self.destination.port)
|
||||||
self.nodeid = randomBytes(8)
|
self.nodeid = helper_random.randomBytes(8)
|
||||||
elif address is not None and sock is not None:
|
elif address is not None and sock is not None:
|
||||||
TLSDispatcher.__init__(self, sock, server_side=False)
|
TLSDispatcher.__init__(self, sock, server_side=False)
|
||||||
self.isOutbound = True
|
self.isOutbound = True
|
||||||
|
@ -81,17 +80,17 @@ class TCPConnection(BMProto, TLSDispatcher):
|
||||||
self.destination.host, self.destination.port)
|
self.destination.host, self.destination.port)
|
||||||
try:
|
try:
|
||||||
self.local = (
|
self.local = (
|
||||||
protocol.checkIPAddress(
|
self.protocol.checkIPAddress(
|
||||||
protocol.encodeHost(self.destination.host), True)
|
self.protocol.encodeHost(self.destination.host), True)
|
||||||
and not protocol.checkSocksIP(self.destination.host)
|
and not self.protocol.checkSocksIP(self.destination.host)
|
||||||
)
|
)
|
||||||
except socket.error:
|
except socket.error:
|
||||||
# it's probably a hostname
|
# it's probably a hostname
|
||||||
pass
|
pass
|
||||||
self.network_group = protocol.network_group(self.destination.host)
|
self.network_group = self.protocol.network_group(self.destination.host)
|
||||||
ObjectTracker.__init__(self) # pylint: disable=non-parent-init-called
|
ObjectTracker.__init__(self) # pylint: disable=non-parent-init-called
|
||||||
self.bm_proto_reset()
|
self.bm_proto_reset()
|
||||||
self.set_state("bm_header", expectBytes=protocol.Header.size)
|
self.set_state("bm_header", expectBytes=self.protocol.Header.size)
|
||||||
|
|
||||||
def antiIntersectionDelay(self, initial=False):
|
def antiIntersectionDelay(self, initial=False):
|
||||||
"""
|
"""
|
||||||
|
@ -156,7 +155,7 @@ class TCPConnection(BMProto, TLSDispatcher):
|
||||||
def set_connection_fully_established(self):
|
def set_connection_fully_established(self):
|
||||||
"""Initiate inventory synchronisation."""
|
"""Initiate inventory synchronisation."""
|
||||||
if not self.isOutbound and not self.local:
|
if not self.isOutbound and not self.local:
|
||||||
state.clientHasReceivedIncomingConnections = True
|
self.pool.state.clientHasReceivedIncomingConnections = True
|
||||||
UISignalQueue.put(('setStatusIcon', 'green'))
|
UISignalQueue.put(('setStatusIcon', 'green'))
|
||||||
UISignalQueue.put((
|
UISignalQueue.put((
|
||||||
'updateNetworkStatusTab', (self.isOutbound, True, self.destination)
|
'updateNetworkStatusTab', (self.isOutbound, True, self.destination)
|
||||||
|
@ -164,7 +163,7 @@ class TCPConnection(BMProto, TLSDispatcher):
|
||||||
self.antiIntersectionDelay(True)
|
self.antiIntersectionDelay(True)
|
||||||
self.fullyEstablished = True
|
self.fullyEstablished = True
|
||||||
# The connection having host suitable for knownnodes
|
# The connection having host suitable for knownnodes
|
||||||
if self.isOutbound or not self.local and not state.socksIP:
|
if self.isOutbound or not self.local and not self.pool.state.socksIP:
|
||||||
knownnodes.increaseRating(self.destination)
|
knownnodes.increaseRating(self.destination)
|
||||||
knownnodes.addKnownNode(
|
knownnodes.addKnownNode(
|
||||||
self.streams, self.destination, time.time())
|
self.streams, self.destination, time.time())
|
||||||
|
@ -177,7 +176,7 @@ class TCPConnection(BMProto, TLSDispatcher):
|
||||||
# We are going to share a maximum number of 1000 addrs (per overlapping
|
# We are going to share a maximum number of 1000 addrs (per overlapping
|
||||||
# stream) with our peer. 500 from overlapping streams, 250 from the
|
# stream) with our peer. 500 from overlapping streams, 250 from the
|
||||||
# left child stream, and 250 from the right child stream.
|
# left child stream, and 250 from the right child stream.
|
||||||
maxAddrCount = BMConfigParser().safeGetInt(
|
maxAddrCount = self.pool.config.safeGetInt(
|
||||||
"bitmessagesettings", "maxaddrperstreamsend", 500)
|
"bitmessagesettings", "maxaddrperstreamsend", 500)
|
||||||
|
|
||||||
templist = []
|
templist = []
|
||||||
|
@ -205,7 +204,7 @@ class TCPConnection(BMProto, TLSDispatcher):
|
||||||
for peer, params in addrs[substream]:
|
for peer, params in addrs[substream]:
|
||||||
templist.append((substream, peer, params["lastseen"]))
|
templist.append((substream, peer, params["lastseen"]))
|
||||||
if templist:
|
if templist:
|
||||||
self.append_write_buf(assemble_addr(templist))
|
self.append_write_buf(self.protocol.assembleAddrMessage(templist))
|
||||||
|
|
||||||
def sendBigInv(self):
|
def sendBigInv(self):
|
||||||
"""
|
"""
|
||||||
|
@ -219,8 +218,8 @@ class TCPConnection(BMProto, TLSDispatcher):
|
||||||
logger.debug(
|
logger.debug(
|
||||||
'Sending huge inv message with %i objects to just this'
|
'Sending huge inv message with %i objects to just this'
|
||||||
' one peer', objectCount)
|
' one peer', objectCount)
|
||||||
self.append_write_buf(protocol.CreatePacket(
|
self.append_write_buf(self.protocol.CreatePacket(
|
||||||
'inv', addresses.encodeVarint(objectCount) + payload))
|
'inv', self.protocol.encodeVarint(objectCount) + payload))
|
||||||
|
|
||||||
# Select all hashes for objects in this stream.
|
# Select all hashes for objects in this stream.
|
||||||
bigInvList = {}
|
bigInvList = {}
|
||||||
|
@ -263,12 +262,11 @@ class TCPConnection(BMProto, TLSDispatcher):
|
||||||
'%s:%i: Connection failed: %s',
|
'%s:%i: Connection failed: %s',
|
||||||
self.destination.host, self.destination.port, e)
|
self.destination.host, self.destination.port, e)
|
||||||
return
|
return
|
||||||
self.nodeid = randomBytes(8)
|
self.nodeid = helper_random.randomBytes(8)
|
||||||
self.append_write_buf(
|
self.append_write_buf(
|
||||||
protocol.assembleVersionMessage(
|
self.protocol.assembleVersionMessage(
|
||||||
self.destination.host, self.destination.port,
|
self.destination.host, self.destination.port,
|
||||||
connectionpool.BMConnectionPool().streams,
|
self.pool.streams, False, nodeid=self.nodeid))
|
||||||
False, nodeid=self.nodeid))
|
|
||||||
self.connectedAt = time.time()
|
self.connectedAt = time.time()
|
||||||
receiveDataQueue.put(self.destination)
|
receiveDataQueue.put(self.destination)
|
||||||
|
|
||||||
|
@ -283,7 +281,8 @@ class TCPConnection(BMProto, TLSDispatcher):
|
||||||
|
|
||||||
def handle_close(self):
|
def handle_close(self):
|
||||||
"""Callback for connection being closed."""
|
"""Callback for connection being closed."""
|
||||||
host_is_global = self.isOutbound or not self.local and not state.socksIP
|
host_is_global = (
|
||||||
|
self.isOutbound or not self.local and not self.pool.state.socksIP)
|
||||||
if self.fullyEstablished:
|
if self.fullyEstablished:
|
||||||
UISignalQueue.put((
|
UISignalQueue.put((
|
||||||
'updateNetworkStatusTab',
|
'updateNetworkStatusTab',
|
||||||
|
@ -303,9 +302,9 @@ class TCPConnection(BMProto, TLSDispatcher):
|
||||||
class Socks5BMConnection(Socks5Connection, TCPConnection):
|
class Socks5BMConnection(Socks5Connection, TCPConnection):
|
||||||
"""SOCKS5 wrapper for TCP connections"""
|
"""SOCKS5 wrapper for TCP connections"""
|
||||||
|
|
||||||
def __init__(self, address):
|
def __init__(self, pool, address):
|
||||||
Socks5Connection.__init__(self, address=address)
|
Socks5Connection.__init__(self, address=address)
|
||||||
TCPConnection.__init__(self, address=address, sock=self.socket)
|
TCPConnection.__init__(self, pool, address=address, sock=self.socket)
|
||||||
self.set_state("init")
|
self.set_state("init")
|
||||||
|
|
||||||
def state_proxy_handshake_done(self):
|
def state_proxy_handshake_done(self):
|
||||||
|
@ -314,38 +313,23 @@ class Socks5BMConnection(Socks5Connection, TCPConnection):
|
||||||
Bitmessage handshake to peer.
|
Bitmessage handshake to peer.
|
||||||
"""
|
"""
|
||||||
Socks5Connection.state_proxy_handshake_done(self)
|
Socks5Connection.state_proxy_handshake_done(self)
|
||||||
self.nodeid = randomBytes(8)
|
self.nodeid = helper_random.randomBytes(8)
|
||||||
self.append_write_buf(
|
self.append_write_buf(
|
||||||
protocol.assembleVersionMessage(
|
self.protocol.assembleVersionMessage(
|
||||||
self.destination.host, self.destination.port,
|
self.destination.host, self.destination.port,
|
||||||
connectionpool.BMConnectionPool().streams,
|
self.pool.streams,
|
||||||
False, nodeid=self.nodeid))
|
False, nodeid=self.nodeid))
|
||||||
self.set_state("bm_header", expectBytes=protocol.Header.size)
|
self.set_state("bm_header", expectBytes=self.protocol.Header.size)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class Socks4aBMConnection(Socks4aConnection, TCPConnection):
|
class Socks4aBMConnection(Socks4aConnection, Socks5BMConnection, TCPConnection):
|
||||||
"""SOCKS4a wrapper for TCP connections"""
|
"""SOCKS4a wrapper for TCP connections"""
|
||||||
|
|
||||||
def __init__(self, address):
|
def __init__(self, pool, address):
|
||||||
Socks4aConnection.__init__(self, address=address)
|
Socks4aConnection.__init__(self, address=address)
|
||||||
TCPConnection.__init__(self, address=address, sock=self.socket)
|
Socks5BMConnection.__init__(
|
||||||
self.set_state("init")
|
self, pool, address=address, sock=self.socket)
|
||||||
|
|
||||||
def state_proxy_handshake_done(self):
|
|
||||||
"""
|
|
||||||
State when SOCKS4a connection succeeds, we need to send a
|
|
||||||
Bitmessage handshake to peer.
|
|
||||||
"""
|
|
||||||
Socks4aConnection.state_proxy_handshake_done(self)
|
|
||||||
self.nodeid = randomBytes(8)
|
|
||||||
self.append_write_buf(
|
|
||||||
protocol.assembleVersionMessage(
|
|
||||||
self.destination.host, self.destination.port,
|
|
||||||
connectionpool.BMConnectionPool().streams,
|
|
||||||
False, nodeid=self.nodeid))
|
|
||||||
self.set_state("bm_header", expectBytes=protocol.Header.size)
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def bootstrap(connection_class):
|
def bootstrap(connection_class):
|
||||||
|
@ -354,8 +338,8 @@ def bootstrap(connection_class):
|
||||||
"""Base class for bootstrappers"""
|
"""Base class for bootstrappers"""
|
||||||
_connection_base = connection_class
|
_connection_base = connection_class
|
||||||
|
|
||||||
def __init__(self, host, port):
|
def __init__(self, pool, host, port):
|
||||||
self._connection_base.__init__(self, Peer(host, port))
|
self._connection_base.__init__(self, pool, Peer(host, port))
|
||||||
self.close_reason = self._succeed = False
|
self.close_reason = self._succeed = False
|
||||||
|
|
||||||
def bm_command_addr(self):
|
def bm_command_addr(self):
|
||||||
|
@ -384,65 +368,3 @@ def bootstrap(connection_class):
|
||||||
knownnodes.knownNodesActual = False
|
knownnodes.knownNodesActual = False
|
||||||
|
|
||||||
return Bootstrapper
|
return Bootstrapper
|
||||||
|
|
||||||
|
|
||||||
class TCPServer(AdvancedDispatcher):
|
|
||||||
"""TCP connection server for Bitmessage protocol"""
|
|
||||||
|
|
||||||
def __init__(self, host='127.0.0.1', port=8444):
|
|
||||||
if not hasattr(self, '_map'):
|
|
||||||
AdvancedDispatcher.__init__(self)
|
|
||||||
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
||||||
self.set_reuse_addr()
|
|
||||||
for attempt in range(50):
|
|
||||||
try:
|
|
||||||
if attempt > 0:
|
|
||||||
logger.warning('Failed to bind on port %s', port)
|
|
||||||
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:
|
|
||||||
logger.warning('Setting port to %s', port)
|
|
||||||
BMConfigParser().set(
|
|
||||||
'bitmessagesettings', 'port', str(port))
|
|
||||||
BMConfigParser().save()
|
|
||||||
break
|
|
||||||
self.destination = Peer(host, port)
|
|
||||||
self.bound = True
|
|
||||||
self.listen(5)
|
|
||||||
|
|
||||||
def is_bound(self):
|
|
||||||
"""Is the socket bound?"""
|
|
||||||
try:
|
|
||||||
return self.bound
|
|
||||||
except AttributeError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def handle_accept(self):
|
|
||||||
"""Incoming connection callback"""
|
|
||||||
try:
|
|
||||||
sock = self.accept()[0]
|
|
||||||
except (TypeError, IndexError):
|
|
||||||
return
|
|
||||||
|
|
||||||
state.ownAddresses[Peer(*sock.getsockname())] = True
|
|
||||||
if (
|
|
||||||
len(connectionpool.BMConnectionPool())
|
|
||||||
> BMConfigParser().safeGetInt(
|
|
||||||
'bitmessagesettings', 'maxtotalconnections')
|
|
||||||
+ BMConfigParser().safeGetInt(
|
|
||||||
'bitmessagesettings', 'maxbootstrapconnections') + 10
|
|
||||||
):
|
|
||||||
# 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
|
|
||||||
|
|
|
@ -11,8 +11,7 @@ class StoppableThread(threading.Thread):
|
||||||
name = None
|
name = None
|
||||||
logger = logging.getLogger('default')
|
logger = logging.getLogger('default')
|
||||||
|
|
||||||
def __init__(self, state, name=None):
|
def __init__(self, name=None):
|
||||||
self.state = state
|
|
||||||
if name:
|
if name:
|
||||||
self.name = name
|
self.name = name
|
||||||
super(StoppableThread, self).__init__(name=self.name)
|
super(StoppableThread, self).__init__(name=self.name)
|
||||||
|
@ -27,6 +26,18 @@ class StoppableThread(threading.Thread):
|
||||||
self.stop.set()
|
self.stop.set()
|
||||||
|
|
||||||
|
|
||||||
|
class StatefulThread(StoppableThread):
|
||||||
|
"""
|
||||||
|
Base class for the network thread which uses protocol,
|
||||||
|
app config or state and initialized with an app object.
|
||||||
|
"""
|
||||||
|
def __init__(self, app, name=None):
|
||||||
|
self.state = app.state
|
||||||
|
self.config = app.config
|
||||||
|
self.protocol = app.protocol
|
||||||
|
super(StatefulThread, self).__init__(name=name or self.name)
|
||||||
|
|
||||||
|
|
||||||
class BusyError(threading.ThreadError):
|
class BusyError(threading.ThreadError):
|
||||||
"""
|
"""
|
||||||
Thread error raised when another connection holds the lock
|
Thread error raised when another connection holds the lock
|
||||||
|
|
|
@ -7,8 +7,9 @@ import socket
|
||||||
import ssl
|
import ssl
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
import paths # magic import
|
||||||
|
|
||||||
import network.asyncore_pollchoose as asyncore
|
import network.asyncore_pollchoose as asyncore
|
||||||
import paths
|
|
||||||
from network.advanceddispatcher import AdvancedDispatcher
|
from network.advanceddispatcher import AdvancedDispatcher
|
||||||
from queues import receiveDataQueue
|
from queues import receiveDataQueue
|
||||||
|
|
||||||
|
|
|
@ -5,13 +5,13 @@ import logging
|
||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import protocol
|
from queues import receiveDataQueue # magic import
|
||||||
import state
|
|
||||||
from bmproto import BMProto
|
from bmproto import BMProto
|
||||||
from constants import MAX_TIME_OFFSET
|
from constants import MAX_TIME_OFFSET
|
||||||
from node import Peer
|
from node import Peer
|
||||||
from objectracker import ObjectTracker
|
from objectracker import ObjectTracker
|
||||||
from queues import receiveDataQueue
|
|
||||||
|
|
||||||
logger = logging.getLogger('default')
|
logger = logging.getLogger('default')
|
||||||
|
|
||||||
|
@ -20,9 +20,10 @@ class UDPSocket(BMProto): # pylint: disable=too-many-instance-attributes
|
||||||
"""Bitmessage protocol over UDP (class)"""
|
"""Bitmessage protocol over UDP (class)"""
|
||||||
port = 8444
|
port = 8444
|
||||||
|
|
||||||
def __init__(self, host=None, sock=None, announcing=False):
|
def __init__(self, pool, host=None, sock=None, announcing=False):
|
||||||
# pylint: disable=bad-super-call
|
self.pool = pool
|
||||||
super(BMProto, self).__init__(sock=sock)
|
self.protocol = pool.protocol
|
||||||
|
super(UDPSocket, self).__init__(sock=sock)
|
||||||
self.verackReceived = True
|
self.verackReceived = True
|
||||||
self.verackSent = True
|
self.verackSent = True
|
||||||
# .. todo:: sort out streams
|
# .. todo:: sort out streams
|
||||||
|
@ -48,7 +49,7 @@ class UDPSocket(BMProto): # pylint: disable=too-many-instance-attributes
|
||||||
self.connecting = False
|
self.connecting = False
|
||||||
self.connected = True
|
self.connected = True
|
||||||
self.announcing = announcing
|
self.announcing = announcing
|
||||||
self.set_state("bm_header", expectBytes=protocol.Header.size)
|
self.set_state("bm_header", expectBytes=self.protocol.Header.size)
|
||||||
|
|
||||||
def set_socket_reuse(self):
|
def set_socket_reuse(self):
|
||||||
"""Set socket reuse option"""
|
"""Set socket reuse option"""
|
||||||
|
@ -78,8 +79,8 @@ class UDPSocket(BMProto): # pylint: disable=too-many-instance-attributes
|
||||||
return True
|
return True
|
||||||
remoteport = False
|
remoteport = False
|
||||||
for seenTime, stream, _, ip, port in addresses:
|
for seenTime, stream, _, ip, port in addresses:
|
||||||
decodedIP = protocol.checkIPAddress(str(ip))
|
decodedIP = self.protocol.checkIPAddress(str(ip))
|
||||||
if stream not in state.streamsInWhichIAmParticipating:
|
if stream not in self.pool.state.streamsInWhichIAmParticipating:
|
||||||
continue
|
continue
|
||||||
if (seenTime < time.time() - MAX_TIME_OFFSET
|
if (seenTime < time.time() - MAX_TIME_OFFSET
|
||||||
or seenTime > time.time() + MAX_TIME_OFFSET):
|
or seenTime > time.time() + MAX_TIME_OFFSET):
|
||||||
|
@ -93,8 +94,8 @@ class UDPSocket(BMProto): # pylint: disable=too-many-instance-attributes
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"received peer discovery from %s:%i (port %i):",
|
"received peer discovery from %s:%i (port %i):",
|
||||||
self.destination.host, self.destination.port, remoteport)
|
self.destination.host, self.destination.port, remoteport)
|
||||||
state.discoveredPeers[Peer(self.destination.host, remoteport)] = \
|
self.pool.state.discoveredPeers[
|
||||||
time.time()
|
Peer(self.destination.host, remoteport)] = time.time()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def bm_command_portcheck(self):
|
def bm_command_portcheck(self):
|
||||||
|
@ -129,8 +130,8 @@ class UDPSocket(BMProto): # pylint: disable=too-many-instance-attributes
|
||||||
return
|
return
|
||||||
|
|
||||||
self.destination = Peer(*addr)
|
self.destination = Peer(*addr)
|
||||||
encodedAddr = protocol.encodeHost(addr[0])
|
encodedAddr = self.protocol.encodeHost(addr[0])
|
||||||
self.local = bool(protocol.checkIPAddress(encodedAddr, True))
|
self.local = bool(self.protocol.checkIPAddress(encodedAddr, True))
|
||||||
# overwrite the old buffer to avoid mixing data and so that
|
# overwrite the old buffer to avoid mixing data and so that
|
||||||
# self.local works correctly
|
# self.local works correctly
|
||||||
self.read_buf[0:] = recdata
|
self.read_buf[0:] = recdata
|
||||||
|
|
|
@ -4,15 +4,14 @@
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import helper_random
|
import helper_random
|
||||||
import protocol
|
|
||||||
from inventory import Inventory
|
from inventory import Inventory
|
||||||
|
# TODo: above two should also be supplied with app or app.protocol
|
||||||
from network.connectionpool import BMConnectionPool
|
from network.connectionpool import BMConnectionPool
|
||||||
from network.dandelion import Dandelion
|
from network.dandelion import Dandelion
|
||||||
from randomtrackingdict import RandomTrackingDict
|
from threads import StatefulThread
|
||||||
from threads import StoppableThread
|
|
||||||
|
|
||||||
|
|
||||||
class UploadThread(StoppableThread):
|
class UploadThread(StatefulThread):
|
||||||
"""
|
"""
|
||||||
This is a thread that uploads the objects that the peers requested from me
|
This is a thread that uploads the objects that the peers requested from me
|
||||||
"""
|
"""
|
||||||
|
@ -34,7 +33,7 @@ class UploadThread(StoppableThread):
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
request = i.pendingUpload.randomKeys(
|
request = i.pendingUpload.randomKeys(
|
||||||
RandomTrackingDict.maxPending)
|
self.protocol.RandomTrackingDict.maxPending)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
continue
|
continue
|
||||||
payload = bytearray()
|
payload = bytearray()
|
||||||
|
@ -49,7 +48,7 @@ class UploadThread(StoppableThread):
|
||||||
i.destination)
|
i.destination)
|
||||||
break
|
break
|
||||||
try:
|
try:
|
||||||
payload.extend(protocol.CreatePacket(
|
payload.extend(self.protocol.CreatePacket(
|
||||||
'object', Inventory()[chunk].payload))
|
'object', Inventory()[chunk].payload))
|
||||||
chunk_count += 1
|
chunk_count += 1
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
|
|
@ -22,8 +22,14 @@ from bmconfigparser import BMConfigParser
|
||||||
from debug import logger
|
from debug import logger
|
||||||
from fallback import RIPEMD160Hash
|
from fallback import RIPEMD160Hash
|
||||||
from helper_sql import sqlExecute
|
from helper_sql import sqlExecute
|
||||||
|
from network.node import Peer
|
||||||
|
from randomtrackingdict import RandomTrackingDict
|
||||||
from version import softwareVersion
|
from version import softwareVersion
|
||||||
|
|
||||||
|
|
||||||
|
#: protocol specification says max 1000 addresses in one addr command
|
||||||
|
MAX_ADDR_COUNT = 1000
|
||||||
|
|
||||||
# Service flags
|
# Service flags
|
||||||
#: This is a normal network node
|
#: This is a normal network node
|
||||||
NODE_NETWORK = 1
|
NODE_NETWORK = 1
|
||||||
|
@ -295,6 +301,28 @@ def CreatePacket(command, payload=b''):
|
||||||
return bytes(b)
|
return bytes(b)
|
||||||
|
|
||||||
|
|
||||||
|
def assembleAddrMessage(peerList):
|
||||||
|
"""Create address command"""
|
||||||
|
if isinstance(peerList, Peer):
|
||||||
|
peerList = [peerList]
|
||||||
|
if not peerList:
|
||||||
|
return b''
|
||||||
|
retval = b''
|
||||||
|
for i in range(0, len(peerList), MAX_ADDR_COUNT):
|
||||||
|
payload = encodeVarint(len(peerList[i:i + MAX_ADDR_COUNT]))
|
||||||
|
for stream, peer, timestamp in peerList[i:i + MAX_ADDR_COUNT]:
|
||||||
|
# 64-bit time
|
||||||
|
payload += pack('>Q', timestamp)
|
||||||
|
payload += pack('>I', stream)
|
||||||
|
# service bit flags offered by this node
|
||||||
|
payload += pack('>q', 1)
|
||||||
|
payload += encodeHost(peer.host)
|
||||||
|
# remote port
|
||||||
|
payload += pack('>H', peer.port)
|
||||||
|
retval += CreatePacket('addr', payload)
|
||||||
|
return retval
|
||||||
|
|
||||||
|
|
||||||
def assembleVersionMessage(
|
def assembleVersionMessage(
|
||||||
remoteHost, remotePort, participatingStreams, server=False, nodeid=None
|
remoteHost, remotePort, participatingStreams, server=False, nodeid=None
|
||||||
):
|
):
|
||||||
|
|
|
@ -5,7 +5,7 @@ Singleton decorator definition
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
|
|
||||||
def Singleton(cls):
|
def Singleton(cls, *args):
|
||||||
"""
|
"""
|
||||||
Decorator implementing the singleton pattern:
|
Decorator implementing the singleton pattern:
|
||||||
it restricts the instantiation of a class to one "single" instance.
|
it restricts the instantiation of a class to one "single" instance.
|
||||||
|
@ -14,9 +14,9 @@ def Singleton(cls):
|
||||||
|
|
||||||
# https://github.com/sphinx-doc/sphinx/issues/3783
|
# https://github.com/sphinx-doc/sphinx/issues/3783
|
||||||
@wraps(cls)
|
@wraps(cls)
|
||||||
def getinstance():
|
def getinstance(*args):
|
||||||
"""Find an instance or save newly created one"""
|
"""Find an instance or save newly created one"""
|
||||||
if cls not in instances:
|
if cls not in instances:
|
||||||
instances[cls] = cls()
|
instances[cls] = cls(*args)
|
||||||
return instances[cls]
|
return instances[cls]
|
||||||
return getinstance
|
return getinstance
|
||||||
|
|
Reference in New Issue
Block a user