Formatted protocol and its docstrings
This commit is contained in:
parent
2a165380bb
commit
f18f534c48
139
src/protocol.py
139
src/protocol.py
|
@ -1,7 +1,8 @@
|
||||||
# pylint: disable=too-many-boolean-expressions,too-many-return-statements,too-many-locals,too-many-statements
|
|
||||||
"""
|
"""
|
||||||
Low-level protocol-related functions.
|
Low-level protocol-related functions.
|
||||||
"""
|
"""
|
||||||
|
# pylint: disable=too-many-boolean-expressions,too-many-return-statements
|
||||||
|
# pylint: disable=too-many-locals,too-many-statements
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import hashlib
|
import hashlib
|
||||||
|
@ -9,7 +10,6 @@ import random
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import traceback
|
|
||||||
from binascii import hexlify
|
from binascii import hexlify
|
||||||
from struct import pack, unpack, Struct
|
from struct import pack, unpack, Struct
|
||||||
|
|
||||||
|
@ -24,10 +24,18 @@ from fallback import RIPEMD160Hash
|
||||||
from helper_sql import sqlExecute
|
from helper_sql import sqlExecute
|
||||||
from version import softwareVersion
|
from version import softwareVersion
|
||||||
|
|
||||||
|
|
||||||
# Service flags
|
# Service flags
|
||||||
|
#: This is a normal network node
|
||||||
NODE_NETWORK = 1
|
NODE_NETWORK = 1
|
||||||
|
#: This node supports SSL/TLS in the current connect (python < 2.7.9
|
||||||
|
#: only supports an SSL client, so in that case it would only have this
|
||||||
|
#: on when the connection is a client).
|
||||||
NODE_SSL = 2
|
NODE_SSL = 2
|
||||||
|
# (Proposal) This node may do PoW on behalf of some its peers
|
||||||
|
# (PoW offloading/delegating), but it doesn't have to. Clients may have
|
||||||
|
# to meet additional requirements (e.g. TLS authentication)
|
||||||
|
# NODE_POW = 4
|
||||||
|
#: Node supports dandelion
|
||||||
NODE_DANDELION = 8
|
NODE_DANDELION = 8
|
||||||
|
|
||||||
# Bitfield flags
|
# Bitfield flags
|
||||||
|
@ -89,7 +97,8 @@ def isBitSetWithinBitfield(fourByteString, n):
|
||||||
def encodeHost(host):
|
def encodeHost(host):
|
||||||
"""Encode a given host to be used in low-level socket operations"""
|
"""Encode a given host to be used in low-level socket operations"""
|
||||||
if host.find('.onion') > -1:
|
if host.find('.onion') > -1:
|
||||||
return '\xfd\x87\xd8\x7e\xeb\x43' + base64.b32decode(host.split(".")[0], True)
|
return '\xfd\x87\xd8\x7e\xeb\x43' + base64.b32decode(
|
||||||
|
host.split(".")[0], True)
|
||||||
elif host.find(':') == -1:
|
elif host.find(':') == -1:
|
||||||
return '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' + \
|
return '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' + \
|
||||||
socket.inet_aton(host)
|
socket.inet_aton(host)
|
||||||
|
@ -134,7 +143,10 @@ def network_group(host):
|
||||||
|
|
||||||
|
|
||||||
def checkIPAddress(host, private=False):
|
def checkIPAddress(host, private=False):
|
||||||
"""Returns hostStandardFormat if it is a valid IP address, otherwise returns False"""
|
"""
|
||||||
|
Returns hostStandardFormat if it is a valid IP address,
|
||||||
|
otherwise returns False
|
||||||
|
"""
|
||||||
if host[0:12] == '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF':
|
if host[0:12] == '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF':
|
||||||
hostStandardFormat = socket.inet_ntop(socket.AF_INET, host[12:])
|
hostStandardFormat = socket.inet_ntop(socket.AF_INET, host[12:])
|
||||||
return checkIPv4Address(host[12:], hostStandardFormat, private)
|
return checkIPv4Address(host[12:], hostStandardFormat, private)
|
||||||
|
@ -150,35 +162,46 @@ def checkIPAddress(host, private=False):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return False
|
return False
|
||||||
if hostStandardFormat == "":
|
if hostStandardFormat == "":
|
||||||
# This can happen on Windows systems which are not 64-bit compatible
|
# This can happen on Windows systems which are
|
||||||
# so let us drop the IPv6 address.
|
# not 64-bit compatible so let us drop the IPv6 address.
|
||||||
return False
|
return False
|
||||||
return checkIPv6Address(host, hostStandardFormat, private)
|
return checkIPv6Address(host, hostStandardFormat, private)
|
||||||
|
|
||||||
|
|
||||||
def checkIPv4Address(host, hostStandardFormat, private=False):
|
def checkIPv4Address(host, hostStandardFormat, private=False):
|
||||||
"""Returns hostStandardFormat if it is an IPv4 address, otherwise returns False"""
|
"""
|
||||||
|
Returns hostStandardFormat if it is an IPv4 address,
|
||||||
|
otherwise returns False
|
||||||
|
"""
|
||||||
if host[0] == '\x7F': # 127/8
|
if host[0] == '\x7F': # 127/8
|
||||||
if not private:
|
if not private:
|
||||||
logger.debug('Ignoring IP address in loopback range: %s', hostStandardFormat)
|
logger.debug(
|
||||||
|
'Ignoring IP address in loopback range: %s',
|
||||||
|
hostStandardFormat)
|
||||||
return hostStandardFormat if private else False
|
return hostStandardFormat if private else False
|
||||||
if host[0] == '\x0A': # 10/8
|
if host[0] == '\x0A': # 10/8
|
||||||
if not private:
|
if not private:
|
||||||
logger.debug('Ignoring IP address in private range: %s', hostStandardFormat)
|
logger.debug(
|
||||||
|
'Ignoring IP address in private range: %s', hostStandardFormat)
|
||||||
return hostStandardFormat if private else False
|
return hostStandardFormat if private else False
|
||||||
if host[0:2] == '\xC0\xA8': # 192.168/16
|
if host[0:2] == '\xC0\xA8': # 192.168/16
|
||||||
if not private:
|
if not private:
|
||||||
logger.debug('Ignoring IP address in private range: %s', hostStandardFormat)
|
logger.debug(
|
||||||
|
'Ignoring IP address in private range: %s', hostStandardFormat)
|
||||||
return hostStandardFormat if private else False
|
return hostStandardFormat if private else False
|
||||||
if host[0:2] >= '\xAC\x10' and host[0:2] < '\xAC\x20': # 172.16/12
|
if host[0:2] >= '\xAC\x10' and host[0:2] < '\xAC\x20': # 172.16/12
|
||||||
if not private:
|
if not private:
|
||||||
logger.debug('Ignoring IP address in private range: %s', hostStandardFormat)
|
logger.debug(
|
||||||
|
'Ignoring IP address in private range: %s', hostStandardFormat)
|
||||||
return hostStandardFormat if private else False
|
return hostStandardFormat if private else False
|
||||||
return False if private else hostStandardFormat
|
return False if private else hostStandardFormat
|
||||||
|
|
||||||
|
|
||||||
def checkIPv6Address(host, hostStandardFormat, private=False):
|
def checkIPv6Address(host, hostStandardFormat, private=False):
|
||||||
"""Returns hostStandardFormat if it is an IPv6 address, otherwise returns False"""
|
"""
|
||||||
|
Returns hostStandardFormat if it is an IPv6 address,
|
||||||
|
otherwise returns False
|
||||||
|
"""
|
||||||
if host == ('\x00' * 15) + '\x01':
|
if host == ('\x00' * 15) + '\x01':
|
||||||
if not private:
|
if not private:
|
||||||
logger.debug('Ignoring loopback address: %s', hostStandardFormat)
|
logger.debug('Ignoring loopback address: %s', hostStandardFormat)
|
||||||
|
@ -189,7 +212,8 @@ def checkIPv6Address(host, hostStandardFormat, private=False):
|
||||||
return hostStandardFormat if private else False
|
return hostStandardFormat if private else False
|
||||||
if (ord(host[0]) & 0xfe) == 0xfc:
|
if (ord(host[0]) & 0xfe) == 0xfc:
|
||||||
if not private:
|
if not private:
|
||||||
logger.debug('Ignoring unique local address: %s', hostStandardFormat)
|
logger.debug(
|
||||||
|
'Ignoring unique local address: %s', hostStandardFormat)
|
||||||
return hostStandardFormat if private else False
|
return hostStandardFormat if private else False
|
||||||
return False if private else hostStandardFormat
|
return False if private else hostStandardFormat
|
||||||
|
|
||||||
|
@ -210,31 +234,29 @@ def haveSSL(server=False):
|
||||||
|
|
||||||
def checkSocksIP(host):
|
def checkSocksIP(host):
|
||||||
"""Predicate to check if we're using a SOCKS proxy"""
|
"""Predicate to check if we're using a SOCKS proxy"""
|
||||||
|
sockshostname = BMConfigParser().safeGet(
|
||||||
|
'bitmessagesettings', 'sockshostname')
|
||||||
try:
|
try:
|
||||||
if state.socksIP is None or not state.socksIP:
|
if not state.socksIP:
|
||||||
state.socksIP = socket.gethostbyname(BMConfigParser().get("bitmessagesettings", "sockshostname"))
|
state.socksIP = socket.gethostbyname(sockshostname)
|
||||||
# uninitialised
|
except NameError: # uninitialised
|
||||||
except NameError:
|
state.socksIP = socket.gethostbyname(sockshostname)
|
||||||
state.socksIP = socket.gethostbyname(BMConfigParser().get("bitmessagesettings", "sockshostname"))
|
except (TypeError, socket.gaierror): # None, resolving failure
|
||||||
# resolving failure
|
state.socksIP = sockshostname
|
||||||
except socket.gaierror:
|
|
||||||
state.socksIP = BMConfigParser().get("bitmessagesettings", "sockshostname")
|
|
||||||
return state.socksIP == host
|
return state.socksIP == host
|
||||||
|
|
||||||
|
|
||||||
def isProofOfWorkSufficient(data,
|
def isProofOfWorkSufficient(
|
||||||
nonceTrialsPerByte=0,
|
data, nonceTrialsPerByte=0, payloadLengthExtraBytes=0, recvTime=0):
|
||||||
payloadLengthExtraBytes=0,
|
|
||||||
recvTime=0):
|
|
||||||
"""
|
"""
|
||||||
Validate an object's Proof of Work using method described in:
|
Validate an object's Proof of Work using method described
|
||||||
https://bitmessage.org/wiki/Proof_of_work
|
`here <https://bitmessage.org/wiki/Proof_of_work>`_
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
int nonceTrialsPerByte (default: from default.py)
|
int nonceTrialsPerByte (default: from `.defaults`)
|
||||||
int payloadLengthExtraBytes (default: from default.py)
|
int payloadLengthExtraBytes (default: from `.defaults`)
|
||||||
float recvTime (optional) UNIX epoch time when object was
|
float recvTime (optional) UNIX epoch time when object was
|
||||||
received from the network (default: current system time)
|
received from the network (default: current system time)
|
||||||
Returns:
|
Returns:
|
||||||
True if PoW valid and sufficient, False in all other cases
|
True if PoW valid and sufficient, False in all other cases
|
||||||
"""
|
"""
|
||||||
|
@ -246,18 +268,20 @@ def isProofOfWorkSufficient(data,
|
||||||
TTL = endOfLifeTime - (int(recvTime) if recvTime else int(time.time()))
|
TTL = endOfLifeTime - (int(recvTime) if recvTime else int(time.time()))
|
||||||
if TTL < 300:
|
if TTL < 300:
|
||||||
TTL = 300
|
TTL = 300
|
||||||
POW, = unpack('>Q', hashlib.sha512(hashlib.sha512(data[
|
POW, = unpack('>Q', hashlib.sha512(hashlib.sha512(
|
||||||
:8] + hashlib.sha512(data[8:]).digest()).digest()).digest()[0:8])
|
data[:8] + hashlib.sha512(data[8:]).digest()
|
||||||
return POW <= 2 ** 64 / (nonceTrialsPerByte *
|
).digest()).digest()[0:8])
|
||||||
(len(data) + payloadLengthExtraBytes +
|
return POW <= 2 ** 64 / (
|
||||||
((TTL * (len(data) + payloadLengthExtraBytes)) / (2 ** 16))))
|
nonceTrialsPerByte * (
|
||||||
|
len(data) + payloadLengthExtraBytes +
|
||||||
|
((TTL * (len(data) + payloadLengthExtraBytes)) / (2 ** 16))))
|
||||||
|
|
||||||
|
|
||||||
# Packet creation
|
# Packet creation
|
||||||
|
|
||||||
|
|
||||||
def CreatePacket(command, payload=''):
|
def CreatePacket(command, payload=''):
|
||||||
"""Construct and return a number of bytes from a payload"""
|
"""Construct and return a packet"""
|
||||||
payload_length = len(payload)
|
payload_length = len(payload)
|
||||||
checksum = hashlib.sha512(payload).digest()[0:4]
|
checksum = hashlib.sha512(payload).digest()[0:4]
|
||||||
|
|
||||||
|
@ -267,8 +291,13 @@ def CreatePacket(command, payload=''):
|
||||||
return bytes(b)
|
return bytes(b)
|
||||||
|
|
||||||
|
|
||||||
def assembleVersionMessage(remoteHost, remotePort, participatingStreams, server=False, nodeid=None):
|
def assembleVersionMessage(
|
||||||
"""Construct the payload of a version message, return the resultng bytes of running CreatePacket() on it"""
|
remoteHost, remotePort, participatingStreams, server=False, nodeid=None
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Construct the payload of a version message,
|
||||||
|
return the resulting bytes of running `CreatePacket` on it
|
||||||
|
"""
|
||||||
payload = ''
|
payload = ''
|
||||||
payload += pack('>L', 3) # protocol version.
|
payload += pack('>L', 3) # protocol version.
|
||||||
# bitflags of the services I offer.
|
# bitflags of the services I offer.
|
||||||
|
@ -280,9 +309,10 @@ def assembleVersionMessage(remoteHost, remotePort, participatingStreams, server=
|
||||||
)
|
)
|
||||||
payload += pack('>q', int(time.time()))
|
payload += pack('>q', int(time.time()))
|
||||||
|
|
||||||
payload += pack(
|
# boolservices of remote connection; ignored by the remote host.
|
||||||
'>q', 1) # boolservices of remote connection; ignored by the remote host.
|
payload += pack('>q', 1)
|
||||||
if checkSocksIP(remoteHost) and server: # prevent leaking of tor outbound IP
|
if checkSocksIP(remoteHost) and server:
|
||||||
|
# prevent leaking of tor outbound IP
|
||||||
payload += encodeHost('127.0.0.1')
|
payload += encodeHost('127.0.0.1')
|
||||||
payload += pack('>H', 8444)
|
payload += pack('>H', 8444)
|
||||||
else:
|
else:
|
||||||
|
@ -301,21 +331,25 @@ def assembleVersionMessage(remoteHost, remotePort, participatingStreams, server=
|
||||||
(NODE_SSL if haveSSL(server) else 0) |
|
(NODE_SSL if haveSSL(server) else 0) |
|
||||||
(NODE_DANDELION if state.dandelion else 0)
|
(NODE_DANDELION if state.dandelion else 0)
|
||||||
)
|
)
|
||||||
# = 127.0.0.1. This will be ignored by the remote host. The actual remote connected IP will be used.
|
# = 127.0.0.1. This will be ignored by the remote host.
|
||||||
payload += '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' + pack('>L', 2130706433)
|
# The actual remote connected IP will be used.
|
||||||
|
payload += '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' + pack(
|
||||||
|
'>L', 2130706433)
|
||||||
# we have a separate extPort and incoming over clearnet
|
# we have a separate extPort and incoming over clearnet
|
||||||
# or outgoing through clearnet
|
# or outgoing through clearnet
|
||||||
extport = BMConfigParser().safeGetInt('bitmessagesettings', 'extport')
|
extport = BMConfigParser().safeGetInt('bitmessagesettings', 'extport')
|
||||||
if (
|
if (
|
||||||
extport and ((server and not checkSocksIP(remoteHost)) or (
|
extport and ((server and not checkSocksIP(remoteHost)) or (
|
||||||
BMConfigParser().get('bitmessagesettings', 'socksproxytype') ==
|
BMConfigParser().get('bitmessagesettings', 'socksproxytype')
|
||||||
'none' and not server))
|
== 'none' and not server))
|
||||||
):
|
):
|
||||||
payload += pack('>H', extport)
|
payload += pack('>H', extport)
|
||||||
elif checkSocksIP(remoteHost) and server: # incoming connection over Tor
|
elif checkSocksIP(remoteHost) and server: # incoming connection over Tor
|
||||||
payload += pack('>H', BMConfigParser().getint('bitmessagesettings', 'onionport'))
|
payload += pack(
|
||||||
|
'>H', BMConfigParser().getint('bitmessagesettings', 'onionport'))
|
||||||
else: # no extport and not incoming over Tor
|
else: # no extport and not incoming over Tor
|
||||||
payload += pack('>H', BMConfigParser().getint('bitmessagesettings', 'port'))
|
payload += pack(
|
||||||
|
'>H', BMConfigParser().getint('bitmessagesettings', 'port'))
|
||||||
|
|
||||||
if nodeid is not None:
|
if nodeid is not None:
|
||||||
payload += nodeid[0:8]
|
payload += nodeid[0:8]
|
||||||
|
@ -339,7 +373,10 @@ def assembleVersionMessage(remoteHost, remotePort, participatingStreams, server=
|
||||||
|
|
||||||
|
|
||||||
def assembleErrorMessage(fatal=0, banTime=0, inventoryVector='', errorText=''):
|
def assembleErrorMessage(fatal=0, banTime=0, inventoryVector='', errorText=''):
|
||||||
"""Construct the payload of an error message, return the resultng bytes of running CreatePacket() on it"""
|
"""
|
||||||
|
Construct the payload of an error message,
|
||||||
|
return the resulting bytes of running `CreatePacket` on it
|
||||||
|
"""
|
||||||
payload = encodeVarint(fatal)
|
payload = encodeVarint(fatal)
|
||||||
payload += encodeVarint(banTime)
|
payload += encodeVarint(banTime)
|
||||||
payload += encodeVarint(len(inventoryVector))
|
payload += encodeVarint(len(inventoryVector))
|
||||||
|
@ -476,7 +513,7 @@ def decryptAndCheckPubkeyPayload(data, address):
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.critical(
|
logger.critical(
|
||||||
'Pubkey decryption was UNsuccessful because of'
|
'Pubkey decryption was UNsuccessful because of'
|
||||||
' an unhandled exception! This is definitely a bug! \n%s',
|
' an unhandled exception! This is definitely a bug!',
|
||||||
traceback.format_exc()
|
exc_info=True
|
||||||
)
|
)
|
||||||
return 'failed'
|
return 'failed'
|
||||||
|
|
Reference in New Issue
Block a user