2017-03-11 11:12:08 +01:00
import hashlib
import time
2017-04-16 18:27:15 +02:00
from pprint import pprint
2017-03-11 11:12:08 +01:00
import socket
2017-04-16 18:27:15 +02:00
from struct import unpack
2017-03-11 11:12:08 +01:00
from network . advanceddispatcher import AdvancedDispatcher
2017-04-16 18:27:15 +02:00
from network . node import Node
2017-03-11 11:12:08 +01:00
import network . asyncore_pollchoose as asyncore
from network . proxy import Proxy , ProxyError , GeneralProxyError
from network . socks5 import Socks5Connection , Socks5Resolver , Socks5AuthError , Socks5Error
from network . socks4a import Socks4aConnection , Socks4aResolver , Socks4aError
2017-04-16 18:27:15 +02:00
from network . tls import TLSDispatcher
2017-03-11 11:12:08 +01:00
import addresses
2017-04-04 10:46:01 +02:00
from bmconfigparser import BMConfigParser
2017-04-16 18:27:15 +02:00
import shared
2017-03-11 11:12:08 +01:00
import protocol
class BMProtoError ( ProxyError ) : pass
2017-04-04 10:46:01 +02:00
class BMConnection ( TLSDispatcher ) :
2017-03-20 18:32:26 +01:00
# ~1.6 MB which is the maximum possible size of an inv message.
maxMessageSize = 1600100
2017-04-04 10:46:01 +02:00
# protocol specification says max 1000 addresses in one addr command
maxAddrCount = 1000
# protocol specification says max 50000 objects in one inv command
maxObjectCount = 50000
2017-03-20 18:32:26 +01:00
def __init__ ( self , address = None , sock = None ) :
AdvancedDispatcher . __init__ ( self , sock )
self . verackReceived = False
self . verackSent = False
if address is None and sock is not None :
self . destination = self . addr ( )
self . isOutbound = False
2017-04-16 18:27:15 +02:00
TLSDispatcher . __init__ ( self , sock , server_side = True )
2017-03-20 18:32:26 +01:00
print " received connection in background from %s : %i " % ( self . destination [ 0 ] , self . destination [ 1 ] )
else :
self . destination = address
self . isOutbound = True
self . create_socket ( socket . AF_INET , socket . SOCK_STREAM )
self . connect ( self . destination )
2017-04-16 18:27:15 +02:00
TLSDispatcher . __init__ ( self , sock , server_side = False )
2017-03-20 18:32:26 +01:00
print " connecting in background to %s : %i " % ( self . destination [ 0 ] , self . destination [ 1 ] )
def bm_proto_reset ( self ) :
self . magic = None
self . command = None
self . payloadLength = None
self . checksum = None
2017-03-11 11:12:08 +01:00
self . payload = None
2017-03-20 18:32:26 +01:00
self . invalid = False
2017-04-16 18:27:15 +02:00
self . payloadOffset = 0
2017-03-11 11:12:08 +01:00
def state_init ( self ) :
2017-03-20 18:32:26 +01:00
self . bm_proto_reset ( )
2017-04-16 18:27:15 +02:00
self . append_write_buf ( protocol . assembleVersionMessage ( self . destination [ 0 ] , self . destination [ 1 ] , ( 1 , ) , False ) )
2017-03-11 11:12:08 +01:00
if True :
print " Sending version ( %i b) " % len ( self . write_buf )
2017-04-16 18:27:15 +02:00
self . set_state ( " bm_header " )
2017-03-11 11:12:08 +01:00
return False
2017-04-04 10:46:01 +02:00
def state_bm_ready ( self ) :
2017-04-16 18:27:15 +02:00
print " doing bm ready "
2017-04-04 10:46:01 +02:00
self . sendAddr ( )
self . sendBigInv ( )
2017-04-16 18:27:15 +02:00
self . set_state ( " bm_header " )
return False
2017-04-04 10:46:01 +02:00
2017-03-20 18:32:26 +01:00
def state_bm_header ( self ) :
if len ( self . read_buf ) < protocol . Header . size :
print " Length below header size "
2017-03-11 11:12:08 +01:00
return False
2017-03-20 18:32:26 +01:00
self . magic , self . command , self . payloadLength , self . checksum = protocol . Header . unpack ( self . read_buf [ : protocol . Header . size ] )
self . command = self . command . rstrip ( ' \x00 ' )
if self . magic != 0xE9BEB4D9 :
# skip 1 byte in order to sync
self . bm_proto_reset ( )
self . set_state ( " bm_header " , 1 )
print " Bad magic "
if self . payloadLength > BMConnection . maxMessageSize :
self . invalid = True
self . set_state ( " bm_command " , protocol . Header . size )
return True
def state_bm_command ( self ) :
if len ( self . read_buf ) < self . payloadLength :
print " Length below announced object length "
2017-03-11 11:12:08 +01:00
return False
2017-03-20 18:32:26 +01:00
print " received %s ( %i b) " % ( self . command , self . payloadLength )
self . payload = self . read_buf [ : self . payloadLength ]
if self . checksum != hashlib . sha512 ( self . payload ) . digest ( ) [ 0 : 4 ] :
print " Bad checksum, ignoring "
self . invalid = True
2017-04-04 10:46:01 +02:00
retval = True
2017-03-20 18:32:26 +01:00
if not self . invalid :
try :
2017-04-04 10:46:01 +02:00
retval = getattr ( self , " bm_command_ " + str ( self . command ) . lower ( ) ) ( )
2017-03-20 18:32:26 +01:00
except AttributeError :
# unimplemented command
print " unimplemented command %s " % ( self . command )
2017-03-11 11:12:08 +01:00
else :
2017-03-20 18:32:26 +01:00
print " Skipping command %s due to invalid data " % ( self . command )
2017-04-04 10:46:01 +02:00
if retval :
self . set_state ( " bm_header " , self . payloadLength )
self . bm_proto_reset ( )
# else assume the command requires a different state to follow
2017-03-20 18:32:26 +01:00
return True
2017-03-11 11:12:08 +01:00
2017-04-16 18:27:15 +02:00
def decode_payload_string ( self , length ) :
value = self . payload [ self . payloadOffset : self . payloadOffset + length ]
self . payloadOffset + = length
return value
def decode_payload_varint ( self ) :
value , offset = addresses . decodeVarint ( self . payload [ self . payloadOffset : ] )
self . payloadOffset + = offset
return value
def decode_payload_node ( self ) :
services , address , port = self . decode_payload_content ( " Q16sH " )
return Node ( services , address , port )
def decode_payload_content ( self , pattern = " v " ) :
# l = varint indicating the length of the next item
# v = varint (or array)
# H = uint16
# I = uint32
# Q = uint64
# i = net_addr (without time and stream number)
# s = string
# 0-9 = length of the next item
# , = end of array
retval = [ ]
size = 0
insideDigit = False
for i in range ( len ( pattern ) ) :
if pattern [ i ] in " 0123456789 " :
size = size * 10 + int ( pattern [ i ] )
continue
elif pattern [ i ] == " l " :
size = self . decode_payload_varint ( )
continue
if size > 0 :
innerval = [ ]
if pattern [ i ] == " s " :
retval . append ( self . payload [ self . payloadOffset : self . payloadOffset + size ] )
self . payloadOffset + = size
else :
for j in range ( size ) :
if " , " in pattern [ i : ] :
retval . append ( self . decode_payload_content ( pattern [ i : pattern . index ( " , " ) ] ) )
else :
retval . append ( self . decode_payload_content ( pattern [ i : ] ) )
size = 0
else :
if pattern [ i ] == " v " :
retval . append ( self . decode_payload_varint ( ) )
if pattern [ i ] == " i " :
retval . append ( self . decode_payload_node ( ) )
if pattern [ i ] == " H " :
retval . append ( unpack ( " >H " , self . payload [ self . payloadOffset : self . payloadOffset + 2 ] ) [ 0 ] )
self . payloadOffset + = 2
if pattern [ i ] == " I " :
retval . append ( unpack ( " >I " , self . payload [ self . payloadOffset : self . payloadOffset + 4 ] ) [ 0 ] )
self . payloadOffset + = 4
if pattern [ i ] == " Q " :
retval . append ( unpack ( " >Q " , self . payload [ self . payloadOffset : self . payloadOffset + 8 ] ) [ 0 ] )
self . payloadOffset + = 8
return retval
2017-04-04 10:46:01 +02:00
def bm_command_error ( self ) :
2017-04-16 18:27:15 +02:00
fatalStatus , banTime , inventoryVector , errorText = self . decode_payload_content ( " vvlsls " )
2017-04-04 10:46:01 +02:00
def bm_command_getdata ( self ) :
2017-04-16 18:27:15 +02:00
items = self . decode_payload_content ( " l32s " )
#self.antiIntersectionDelay(True) # only handle getdata requests if we have been connected long enough
for i in items :
logger . debug ( ' received getdata request for item: ' + hexlify ( i ) )
if self . objectHashHolderInstance . hasHash ( i ) :
self . antiIntersectionDelay ( )
else :
if i in Inventory ( ) :
self . append_write_buf ( protocol . CreatePacket ( ' object ' , Inventory ( ) [ i ] . payload ) )
else :
#self.antiIntersectionDelay()
logger . warning ( ' %s asked for an object with a getdata which is not in either our memory inventory or our SQL inventory. We probably cleaned it out after advertising it but before they got around to asking for it. ' % ( self . peer , ) )
2017-04-04 10:46:01 +02:00
def bm_command_object ( self ) :
2017-04-16 18:27:15 +02:00
lengthOfTimeWeShouldUseToProcessThisMessage = shared . checkAndShareObjectWithPeers ( self . payload )
self . downloadQueue . task_done ( calculateInventoryHash ( self . payload ) )
def bm_command_addr ( self ) :
addresses = self . decode_payload_content ( " lQbQ16sH " )
2017-04-04 10:46:01 +02:00
def bm_command_ping ( self ) :
2017-04-16 18:27:15 +02:00
self . append_write_buf ( protocol . CreatePacket ( ' pong ' ) )
2017-04-04 10:46:01 +02:00
def bm_command_pong ( self ) :
2017-04-16 18:27:15 +02:00
# nothing really
pass
2017-04-04 10:46:01 +02:00
2017-03-20 18:32:26 +01:00
def bm_command_verack ( self ) :
2017-03-11 11:12:08 +01:00
self . verackReceived = True
2017-04-16 18:27:15 +02:00
if self . verackSent :
if self . isSSL :
self . set_state ( " tls_init " , self . payloadLength )
else :
self . set_state ( " bm_ready " , self . payloadLength )
else :
self . set_state ( " bm_header " , self . payloadLength )
self . bm_proto_reset ( )
return False
2017-03-11 11:12:08 +01:00
2017-03-20 18:32:26 +01:00
def bm_command_version ( self ) :
2017-04-16 18:27:15 +02:00
#self.remoteProtocolVersion, self.services, self.timestamp, padding1, self.myExternalIP, padding2, self.remoteNodeIncomingPort = protocol.VersionPacket.unpack(self.payload[:protocol.VersionPacket.size])
self . remoteProtocolVersion , self . services , self . timestamp , self . sockNode , self . peerNode , self . nonce , self . userAgent , self . streams = self . decode_payload_content ( " IQQiiQlslv " )
self . timeOffset = self . timestamp - int ( time . time ( ) )
2017-03-11 11:12:08 +01:00
print " remoteProtocolVersion: %i " % ( self . remoteProtocolVersion )
print " services: %08X " % ( self . services )
2017-03-20 18:32:26 +01:00
print " time offset: %i " % ( self . timestamp - int ( time . time ( ) ) )
2017-04-16 18:27:15 +02:00
print " my external IP: %s " % ( self . sockNode . address )
print " remote node incoming port: %i " % ( self . peerNode . port )
2017-03-11 11:12:08 +01:00
print " user agent: %s " % ( self . userAgent )
2017-04-04 10:46:01 +02:00
if not self . peerValidityChecks ( ) :
# TODO ABORT
return True
2017-04-16 18:27:15 +02:00
self . append_write_buf ( protocol . CreatePacket ( ' verack ' ) )
2017-04-04 10:46:01 +02:00
self . verackSent = True
if ( ( self . services & protocol . NODE_SSL == protocol . NODE_SSL ) and
protocol . haveSSL ( not self . isOutbound ) ) :
self . isSSL = True
if self . verackReceived :
if self . isSSL :
self . set_state ( " tls_init " , self . payloadLength )
else :
self . set_state ( " bm_ready " , self . payloadLength )
self . bm_proto_reset ( )
return False
def peerValidityChecks ( self ) :
if self . remoteProtocolVersion < 3 :
2017-04-16 18:27:15 +02:00
self . append_write_buf ( protocol . assembleErrorMessage ( fatal = 2 ,
errorText = " Your is using an old protocol. Closing connection. " ) )
2017-04-04 10:46:01 +02:00
logger . debug ( ' Closing connection to old protocol version %s , node: %s ' ,
str ( self . remoteProtocolVersion ) , str ( self . peer ) )
return False
if self . timeOffset > 3600 :
2017-04-16 18:27:15 +02:00
self . append_write_buf ( protocol . assembleErrorMessage ( fatal = 2 ,
errorText = " Your time is too far in the future compared to mine. Closing connection. " ) )
2017-04-04 10:46:01 +02:00
logger . info ( " %s ' s time is too far in the future ( %s seconds). Closing connection to it. " ,
self . peer , self . timeOffset )
shared . timeOffsetWrongCount + = 1
return False
elif self . timeOffset < - 3600 :
2017-04-16 18:27:15 +02:00
self . append_write_buf ( protocol . assembleErrorMessage ( fatal = 2 ,
errorText = " Your time is too far in the past compared to mine. Closing connection. " ) )
2017-04-04 10:46:01 +02:00
logger . info ( " %s ' s time is too far in the past (timeOffset %s seconds). Closing connection to it. " ,
self . peer , self . timeOffset )
shared . timeOffsetWrongCount + = 1
return False
else :
shared . timeOffsetWrongCount = 0
if len ( self . streams ) == 0 :
2017-04-16 18:27:15 +02:00
self . append_write_buf ( protocol . assembleErrorMessage ( fatal = 2 ,
errorText = " We don ' t have shared stream interests. Closing connection. " ) )
2017-04-04 10:46:01 +02:00
logger . debug ( ' Closed connection to %s because there is no overlapping interest in streams. ' ,
str ( self . peer ) )
return False
2017-03-20 18:32:26 +01:00
return True
2017-03-11 11:12:08 +01:00
2017-04-04 10:46:01 +02:00
def sendAddr ( self ) :
def sendChunk ( ) :
if numberOfAddressesInAddrMessage == 0 :
return
2017-04-16 18:27:15 +02:00
self . append_write_buf ( protocol . CreatePacket ( ' addr ' , \
addresses . encodeVarint ( numberOfAddressesInAddrMessage ) + payload ) )
2017-04-04 10:46:01 +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.
maxAddrCount = BMConfigParser ( ) . safeGetInt ( " bitmessagesettings " , " maxaddrperstreamsend " , 500 )
# init
addressCount = 0
payload = ' '
for stream in self . streams :
addrsInMyStream = { }
addrsInChildStreamLeft = { }
addrsInChildStreamRight = { }
with knownnodes . knownNodesLock :
if len ( knownnodes . knownNodes [ stream ] ) > 0 :
filtered = { k : v for k , v in knownnodes . knownNodes [ stream ] . items ( )
if v > ( int ( time . time ( ) ) - shared . maximumAgeOfNodesThatIAdvertiseToOthers ) }
elemCount = len ( filtered )
if elemCount > maxAddrCount :
elemCount = maxAddrCount
# only if more recent than 3 hours
addrsInMyStream = random . sample ( filtered . items ( ) , elemCount )
# sent 250 only if the remote isn't interested in it
if len ( knownnodes . knownNodes [ stream * 2 ] ) > 0 and stream not in self . streams :
filtered = { k : v for k , v in knownnodes . knownNodes [ stream * 2 ] . items ( )
if v > ( int ( time . time ( ) ) - shared . maximumAgeOfNodesThatIAdvertiseToOthers ) }
elemCount = len ( filtered )
if elemCount > maxAddrCount / 2 :
elemCount = int ( maxAddrCount / 2 )
addrsInChildStreamLeft = random . sample ( filtered . items ( ) , elemCount )
if len ( knownnodes . knownNodes [ ( stream * 2 ) + 1 ] ) > 0 and stream not in self . streams :
filtered = { k : v for k , v in knownnodes . knownNodes [ stream * 2 + 1 ] . items ( )
if v > ( int ( time . time ( ) ) - shared . maximumAgeOfNodesThatIAdvertiseToOthers ) }
elemCount = len ( filtered )
if elemCount > maxAddrCount / 2 :
elemCount = int ( maxAddrCount / 2 )
addrsInChildStreamRight = random . sample ( filtered . items ( ) , elemCount )
for ( HOST , PORT ) , timeLastReceivedMessageFromThisNode in addrsInMyStream :
addressCount + = 1
payload + = pack (
' >Q ' , timeLastReceivedMessageFromThisNode ) # 64-bit time
payload + = pack ( ' >I ' , stream )
payload + = pack (
' >q ' , 1 ) # service bit flags offered by this node
payload + = protocol . encodeHost ( HOST )
payload + = pack ( ' >H ' , PORT ) # remote port
if addressCount > = BMConnection . maxAddrCount :
sendChunk ( )
payload = ' '
addressCount = 0
for ( HOST , PORT ) , timeLastReceivedMessageFromThisNode in addrsInChildStreamLeft :
addressCount + = 1
payload + = pack (
' >Q ' , timeLastReceivedMessageFromThisNode ) # 64-bit time
payload + = pack ( ' >I ' , stream * 2 )
payload + = pack (
' >q ' , 1 ) # service bit flags offered by this node
payload + = protocol . encodeHost ( HOST )
payload + = pack ( ' >H ' , PORT ) # remote port
if addressCount > = BMConnection . maxAddrCount :
sendChunk ( )
payload = ' '
addressCount = 0
for ( HOST , PORT ) , timeLastReceivedMessageFromThisNode in addrsInChildStreamRight :
addressCount + = 1
payload + = pack (
' >Q ' , timeLastReceivedMessageFromThisNode ) # 64-bit time
payload + = pack ( ' >I ' , ( stream * 2 ) + 1 )
payload + = pack (
' >q ' , 1 ) # service bit flags offered by this node
payload + = protocol . encodeHost ( HOST )
payload + = pack ( ' >H ' , PORT ) # remote port
if addressCount > = BMConnection . maxAddrCount :
sendChunk ( )
payload = ' '
addressCount = 0
# flush
sendChunk ( )
def sendBigInv ( self ) :
def sendChunk ( ) :
if objectCount == 0 :
return
payload = encodeVarint ( objectCount ) + payload
logger . debug ( ' Sending huge inv message with %i objects to just this one peer ' ,
str ( numberOfObjects ) )
2017-04-16 18:27:15 +02:00
self . append_write_buf ( protocol . CreatePacket ( ' inv ' , payload ) )
2017-04-04 10:46:01 +02:00
# Select all hashes for objects in this stream.
bigInvList = { }
for stream in self . streams :
for hash in Inventory ( ) . unexpired_hashes_by_stream ( stream ) :
if not self . objectHashHolderInstance . hasHash ( hash ) :
bigInvList [ hash ] = 0
objectCount = 0
payload = ' '
# Now let us start appending all of these hashes together. They will be
# sent out in a big inv message to our new peer.
for hash , storedValue in bigInvList . items ( ) :
payload + = hash
objectCount + = 1
if objectCount > = BMConnection . maxObjectCount :
self . sendChunk ( )
payload = ' '
objectCount = 0
# flush
sendChunk ( )
2017-03-11 11:12:08 +01:00
class Socks5BMConnection ( Socks5Connection , BMConnection ) :
def __init__ ( self , address ) :
Socks5Connection . __init__ ( self , address = address )
def state_socks_handshake_done ( self ) :
BMConnection . state_init ( self )
return False
class Socks4aBMConnection ( Socks4aConnection , BMConnection ) :
def __init__ ( self , address ) :
Socks4aConnection . __init__ ( self , address = address )
def state_socks_handshake_done ( self ) :
BMConnection . state_init ( self )
return False
2017-03-20 18:32:26 +01:00
class BMServer ( AdvancedDispatcher ) :
port = 8444
def __init__ ( self , port = None ) :
if not hasattr ( self , ' _map ' ) :
AdvancedDispatcher . __init__ ( self )
self . create_socket ( socket . AF_INET , socket . SOCK_STREAM )
self . set_reuse_addr ( )
if port is None :
port = BMServer . port
self . bind ( ( ' 127.0.0.1 ' , port ) )
self . connections = 0
self . listen ( 5 )
def handle_accept ( self ) :
pair = self . accept ( )
if pair is not None :
sock , addr = pair
BMConnection ( sock = sock )
2017-03-11 11:12:08 +01:00
if __name__ == " __main__ " :
# initial fill
for host in ( ( " 127.0.0.1 " , 8448 ) , ) :
direct = BMConnection ( host )
while len ( asyncore . socket_map ) > 0 :
print " loop, state = %s " % ( direct . state )
2017-04-16 18:27:15 +02:00
asyncore . loop ( timeout = 10 , count = 1 )
2017-03-11 11:12:08 +01:00
continue
proxy = Socks5BMConnection ( host )
while len ( asyncore . socket_map ) > 0 :
# print "loop, state = %s" % (proxy.state)
2017-04-16 18:27:15 +02:00
asyncore . loop ( timeout = 10 , count = 1 )
2017-03-11 11:12:08 +01:00
proxy = Socks4aBMConnection ( host )
while len ( asyncore . socket_map ) > 0 :
# print "loop, state = %s" % (proxy.state)
2017-04-16 18:27:15 +02:00
asyncore . loop ( timeout = 10 , count = 1 )