Add support for IPv6
It will now listen on an IPv6 socket if possible or fall back to IPv4 if that doesn't work. It will no longer filter out all IPv6 addresses and instead it will only filter out those that point to the local network. It looks like the DNS bootstrapping should just automatically work because getaddrinfo already returns IPv6 addresses from the AAAA record. In order to convert from the ASCII representation of IPv6 addresses and back we need inet_ntop and inet_pton. Python 2 doesn't currently provide these for Windows so instead this patch provides a hot patch to the socket module which wraps WSAStringToAddress and WSAAddressToString using ctypes.
This commit is contained in:
parent
700e3d1f17
commit
7da6ea958f
|
@ -13,6 +13,9 @@ import signal # Used to capture a Ctrl-C keypress so that Bitmessage can shutdo
|
||||||
# The next 3 are used for the API
|
# The next 3 are used for the API
|
||||||
import singleton
|
import singleton
|
||||||
import os
|
import os
|
||||||
|
import socket
|
||||||
|
import ctypes
|
||||||
|
from struct import pack
|
||||||
|
|
||||||
from SimpleXMLRPCServer import SimpleXMLRPCServer
|
from SimpleXMLRPCServer import SimpleXMLRPCServer
|
||||||
from api import MySimpleXMLRPCRequestHandler
|
from api import MySimpleXMLRPCRequestHandler
|
||||||
|
@ -71,6 +74,56 @@ def connectToStream(streamNumber):
|
||||||
a.setup(streamNumber, selfInitiatedConnections)
|
a.setup(streamNumber, selfInitiatedConnections)
|
||||||
a.start()
|
a.start()
|
||||||
|
|
||||||
|
def _fixWinsock():
|
||||||
|
if not ('win32' in sys.platform) and not ('win64' in sys.platform):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Python 2 on Windows doesn't define a wrapper for
|
||||||
|
# socket.inet_ntop but we can make one ourselves using ctypes
|
||||||
|
if not hasattr(socket, 'inet_ntop'):
|
||||||
|
addressToString = ctypes.windll.ws2_32.WSAAddressToStringA
|
||||||
|
def inet_ntop(family, host):
|
||||||
|
if family == socket.AF_INET:
|
||||||
|
if len(host) != 4:
|
||||||
|
raise ValueError("invalid IPv4 host")
|
||||||
|
host = pack("hH4s8s", socket.AF_INET, 0, host, "\0" * 8)
|
||||||
|
elif family == socket.AF_INET6:
|
||||||
|
if len(host) != 16:
|
||||||
|
raise ValueError("invalid IPv6 host")
|
||||||
|
host = pack("hHL16sL", socket.AF_INET6, 0, 0, host, 0)
|
||||||
|
else:
|
||||||
|
raise ValueError("invalid address family")
|
||||||
|
buf = "\0" * 64
|
||||||
|
lengthBuf = pack("I", len(buf))
|
||||||
|
addressToString(host, len(host), None, buf, lengthBuf)
|
||||||
|
return buf[0:buf.index("\0")]
|
||||||
|
socket.inet_ntop = inet_ntop
|
||||||
|
|
||||||
|
# Same for inet_pton
|
||||||
|
if not hasattr(socket, 'inet_pton'):
|
||||||
|
stringToAddress = ctypes.windll.ws2_32.WSAStringToAddressA
|
||||||
|
def inet_pton(family, host):
|
||||||
|
buf = "\0" * 28
|
||||||
|
lengthBuf = pack("I", len(buf))
|
||||||
|
if stringToAddress(str(host),
|
||||||
|
int(family),
|
||||||
|
None,
|
||||||
|
buf,
|
||||||
|
lengthBuf) != 0:
|
||||||
|
raise socket.error("illegal IP address passed to inet_pton")
|
||||||
|
if family == socket.AF_INET:
|
||||||
|
return buf[4:8]
|
||||||
|
elif family == socket.AF_INET6:
|
||||||
|
return buf[8:24]
|
||||||
|
else:
|
||||||
|
raise ValueError("invalid address family")
|
||||||
|
socket.inet_pton = inet_pton
|
||||||
|
|
||||||
|
# These sockopts are needed on for IPv6 support
|
||||||
|
if not hasattr(socket, 'IPPROTO_IPV6'):
|
||||||
|
socket.IPPROTO_IPV6 = 41
|
||||||
|
if not hasattr(socket, 'IPV6_V6ONLY'):
|
||||||
|
socket.IPV6_V6ONLY = 27
|
||||||
|
|
||||||
# This thread, of which there is only one, runs the API.
|
# This thread, of which there is only one, runs the API.
|
||||||
class singleAPI(threading.Thread):
|
class singleAPI(threading.Thread):
|
||||||
|
@ -95,6 +148,8 @@ if shared.useVeryEasyProofOfWorkForTesting:
|
||||||
|
|
||||||
class Main:
|
class Main:
|
||||||
def start(self, daemon=False):
|
def start(self, daemon=False):
|
||||||
|
_fixWinsock()
|
||||||
|
|
||||||
shared.daemon = daemon
|
shared.daemon = daemon
|
||||||
# is the application already running? If yes then exit.
|
# is the application already running? If yes then exit.
|
||||||
thisapp = singleton.singleinstance()
|
thisapp = singleton.singleinstance()
|
||||||
|
|
|
@ -55,7 +55,11 @@ class outgoingSynSender(threading.Thread):
|
||||||
shared.alreadyAttemptedConnectionsListLock.release()
|
shared.alreadyAttemptedConnectionsListLock.release()
|
||||||
timeNodeLastSeen = shared.knownNodes[
|
timeNodeLastSeen = shared.knownNodes[
|
||||||
self.streamNumber][peer]
|
self.streamNumber][peer]
|
||||||
sock = socks.socksocket(socket.AF_INET, socket.SOCK_STREAM)
|
if peer.host.find(':') == -1:
|
||||||
|
address_family = socket.AF_INET
|
||||||
|
else:
|
||||||
|
address_family = socket.AF_INET6
|
||||||
|
sock = socks.socksocket(address_family, socket.SOCK_STREAM)
|
||||||
# This option apparently avoids the TIME_WAIT state so that we
|
# This option apparently avoids the TIME_WAIT state so that we
|
||||||
# can rebind faster
|
# can rebind faster
|
||||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
|
|
@ -532,6 +532,32 @@ class receiveDataThread(threading.Thread):
|
||||||
|
|
||||||
shared.broadcastToSendDataQueues((self.streamNumber, 'advertiseobject', hash))
|
shared.broadcastToSendDataQueues((self.streamNumber, 'advertiseobject', hash))
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def _checkIPv4Address(self, host, hostFromAddrMessage):
|
||||||
|
# print 'hostFromAddrMessage', hostFromAddrMessage
|
||||||
|
if host[0] == '\x7F':
|
||||||
|
print 'Ignoring IP address in loopback range:', hostFromAddrMessage
|
||||||
|
return False
|
||||||
|
if host[0] == '\x0A':
|
||||||
|
print 'Ignoring IP address in private range:', hostFromAddrMessage
|
||||||
|
return False
|
||||||
|
if host[0:2] == '\xC0A8':
|
||||||
|
print 'Ignoring IP address in private range:', hostFromAddrMessage
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _checkIPv6Address(self, host, hostFromAddrMessage):
|
||||||
|
if host == ('\x00' * 15) + '\x01':
|
||||||
|
print 'Ignoring loopback address:', hostFromAddrMessage
|
||||||
|
return False
|
||||||
|
if host[0] == '\xFE' and (ord(host[1]) & 0xc0) == 0x80:
|
||||||
|
print 'Ignoring local address:', hostFromAddrMessage
|
||||||
|
return False
|
||||||
|
if (ord(host[0]) & 0xfe) == 0xfc:
|
||||||
|
print 'Ignoring unique local address:', hostFromAddrMessage
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
# We have received an addr message.
|
# We have received an addr message.
|
||||||
def recaddr(self, data):
|
def recaddr(self, data):
|
||||||
#listOfAddressDetailsToBroadcastToPeers = []
|
#listOfAddressDetailsToBroadcastToPeers = []
|
||||||
|
@ -551,14 +577,11 @@ class receiveDataThread(threading.Thread):
|
||||||
|
|
||||||
for i in range(0, numberOfAddressesIncluded):
|
for i in range(0, numberOfAddressesIncluded):
|
||||||
try:
|
try:
|
||||||
if data[20 + lengthOfNumberOfAddresses + (38 * i):32 + lengthOfNumberOfAddresses + (38 * i)] != '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF':
|
fullHost = data[20 + lengthOfNumberOfAddresses + (38 * i):36 + lengthOfNumberOfAddresses + (38 * i)]
|
||||||
with shared.printLock:
|
|
||||||
print 'Skipping IPv6 address.', repr(data[20 + lengthOfNumberOfAddresses + (38 * i):32 + lengthOfNumberOfAddresses + (38 * i)])
|
|
||||||
continue
|
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
with shared.printLock:
|
with shared.printLock:
|
||||||
sys.stderr.write(
|
sys.stderr.write(
|
||||||
'ERROR TRYING TO UNPACK recaddr (to test for an IPv6 address). Message: %s\n' % str(err))
|
'ERROR TRYING TO UNPACK recaddr (recaddrHost). Message: %s\n' % str(err))
|
||||||
break # giving up on unpacking any more. We should still be connected however.
|
break # giving up on unpacking any more. We should still be connected however.
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -592,17 +615,14 @@ class receiveDataThread(threading.Thread):
|
||||||
break # giving up on unpacking any more. We should still be connected however.
|
break # giving up on unpacking any more. We should still be connected however.
|
||||||
# print 'Within recaddr(): IP', recaddrIP, ', Port',
|
# print 'Within recaddr(): IP', recaddrIP, ', Port',
|
||||||
# recaddrPort, ', i', i
|
# recaddrPort, ', i', i
|
||||||
hostFromAddrMessage = socket.inet_ntoa(data[
|
if fullHost[0:12] == '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF':
|
||||||
32 + lengthOfNumberOfAddresses + (38 * i):36 + lengthOfNumberOfAddresses + (38 * i)])
|
ipv4Host = fullHost[12:]
|
||||||
# print 'hostFromAddrMessage', hostFromAddrMessage
|
hostFromAddrMessage = socket.inet_ntop(socket.AF_INET, ipv4Host)
|
||||||
if data[32 + lengthOfNumberOfAddresses + (38 * i)] == '\x7F':
|
if not self._checkIPv4Address(ipv4Host, hostFromAddrMessage):
|
||||||
print 'Ignoring IP address in loopback range:', hostFromAddrMessage
|
|
||||||
continue
|
continue
|
||||||
if data[32 + lengthOfNumberOfAddresses + (38 * i)] == '\x0A':
|
else:
|
||||||
print 'Ignoring IP address in private range:', hostFromAddrMessage
|
hostFromAddrMessage = socket.inet_ntop(socket.AF_INET6, fullHost)
|
||||||
continue
|
if not self._checkIPv6Address(fullHost, hostFromAddrMessage):
|
||||||
if data[32 + lengthOfNumberOfAddresses + (38 * i):34 + lengthOfNumberOfAddresses + (38 * i)] == '\xC0A8':
|
|
||||||
print 'Ignoring IP address in private range:', hostFromAddrMessage
|
|
||||||
continue
|
continue
|
||||||
timeSomeoneElseReceivedMessageFromThisNode, = unpack('>Q', data[lengthOfNumberOfAddresses + (
|
timeSomeoneElseReceivedMessageFromThisNode, = unpack('>Q', data[lengthOfNumberOfAddresses + (
|
||||||
38 * i):8 + lengthOfNumberOfAddresses + (38 * i)]) # This is the 'time' value in the received addr message. 64-bit.
|
38 * i):8 + lengthOfNumberOfAddresses + (38 * i)]) # This is the 'time' value in the received addr message. 64-bit.
|
||||||
|
@ -688,8 +708,7 @@ class receiveDataThread(threading.Thread):
|
||||||
payload += pack('>I', self.streamNumber)
|
payload += pack('>I', self.streamNumber)
|
||||||
payload += pack(
|
payload += pack(
|
||||||
'>q', 1) # service bit flags offered by this node
|
'>q', 1) # service bit flags offered by this node
|
||||||
payload += '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' + \
|
payload += shared.encodeHost(HOST)
|
||||||
socket.inet_aton(HOST)
|
|
||||||
payload += pack('>H', PORT) # remote port
|
payload += pack('>H', PORT) # remote port
|
||||||
for (HOST, PORT), value in addrsInChildStreamLeft.items():
|
for (HOST, PORT), value in addrsInChildStreamLeft.items():
|
||||||
timeLastReceivedMessageFromThisNode = value
|
timeLastReceivedMessageFromThisNode = value
|
||||||
|
@ -700,8 +719,7 @@ class receiveDataThread(threading.Thread):
|
||||||
payload += pack('>I', self.streamNumber * 2)
|
payload += pack('>I', self.streamNumber * 2)
|
||||||
payload += pack(
|
payload += pack(
|
||||||
'>q', 1) # service bit flags offered by this node
|
'>q', 1) # service bit flags offered by this node
|
||||||
payload += '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' + \
|
payload += shared.encodeHost(HOST)
|
||||||
socket.inet_aton(HOST)
|
|
||||||
payload += pack('>H', PORT) # remote port
|
payload += pack('>H', PORT) # remote port
|
||||||
for (HOST, PORT), value in addrsInChildStreamRight.items():
|
for (HOST, PORT), value in addrsInChildStreamRight.items():
|
||||||
timeLastReceivedMessageFromThisNode = value
|
timeLastReceivedMessageFromThisNode = value
|
||||||
|
@ -712,8 +730,7 @@ class receiveDataThread(threading.Thread):
|
||||||
payload += pack('>I', (self.streamNumber * 2) + 1)
|
payload += pack('>I', (self.streamNumber * 2) + 1)
|
||||||
payload += pack(
|
payload += pack(
|
||||||
'>q', 1) # service bit flags offered by this node
|
'>q', 1) # service bit flags offered by this node
|
||||||
payload += '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' + \
|
payload += shared.encodeHost(HOST)
|
||||||
socket.inet_aton(HOST)
|
|
||||||
payload += pack('>H', PORT) # remote port
|
payload += pack('>H', PORT) # remote port
|
||||||
|
|
||||||
payload = encodeVarint(numberOfAddressesInAddrMessage) + payload
|
payload = encodeVarint(numberOfAddressesInAddrMessage) + payload
|
||||||
|
|
|
@ -113,8 +113,7 @@ class sendDataThread(threading.Thread):
|
||||||
payload += pack('>I', streamNumber)
|
payload += pack('>I', streamNumber)
|
||||||
payload += pack(
|
payload += pack(
|
||||||
'>q', services) # service bit flags offered by this node
|
'>q', services) # service bit flags offered by this node
|
||||||
payload += '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' + \
|
payload += shared.encodeHost(host)
|
||||||
socket.inet_aton(host)
|
|
||||||
payload += pack('>H', port)
|
payload += pack('>H', port)
|
||||||
|
|
||||||
payload = encodeVarint(numberOfAddressesInAddrMessage) + payload
|
payload = encodeVarint(numberOfAddressesInAddrMessage) + payload
|
||||||
|
|
|
@ -4,6 +4,8 @@ import socket
|
||||||
from class_sendDataThread import *
|
from class_sendDataThread import *
|
||||||
from class_receiveDataThread import *
|
from class_receiveDataThread import *
|
||||||
import helper_bootstrap
|
import helper_bootstrap
|
||||||
|
import errno
|
||||||
|
import re
|
||||||
|
|
||||||
# Only one singleListener thread will ever exist. It creates the
|
# Only one singleListener thread will ever exist. It creates the
|
||||||
# receiveDataThread and sendDataThread for each incoming connection. Note
|
# receiveDataThread and sendDataThread for each incoming connection. Note
|
||||||
|
@ -21,6 +23,21 @@ class singleListener(threading.Thread):
|
||||||
def setup(self, selfInitiatedConnections):
|
def setup(self, selfInitiatedConnections):
|
||||||
self.selfInitiatedConnections = selfInitiatedConnections
|
self.selfInitiatedConnections = selfInitiatedConnections
|
||||||
|
|
||||||
|
def _createListenSocket(self, family):
|
||||||
|
HOST = '' # Symbolic name meaning all available interfaces
|
||||||
|
PORT = shared.config.getint('bitmessagesettings', 'port')
|
||||||
|
sock = socket.socket(family, socket.SOCK_STREAM)
|
||||||
|
if family == socket.AF_INET6:
|
||||||
|
# Make sure we can accept both IPv4 and IPv6 connections.
|
||||||
|
# This is the default on everything apart from Windows
|
||||||
|
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
|
||||||
|
# This option apparently avoids the TIME_WAIT state so that we can
|
||||||
|
# rebind faster
|
||||||
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
sock.bind((HOST, PORT))
|
||||||
|
sock.listen(2)
|
||||||
|
return sock
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
while shared.safeConfigGetBoolean('bitmessagesettings', 'dontconnect'):
|
while shared.safeConfigGetBoolean('bitmessagesettings', 'dontconnect'):
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
@ -35,14 +52,22 @@ class singleListener(threading.Thread):
|
||||||
with shared.printLock:
|
with shared.printLock:
|
||||||
print 'Listening for incoming connections.'
|
print 'Listening for incoming connections.'
|
||||||
|
|
||||||
HOST = '' # Symbolic name meaning all available interfaces
|
# First try listening on an IPv6 socket. This should also be
|
||||||
PORT = shared.config.getint('bitmessagesettings', 'port')
|
# able to accept connections on IPv4. If that's not available
|
||||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
# we'll fall back to IPv4-only.
|
||||||
# This option apparently avoids the TIME_WAIT state so that we can
|
try:
|
||||||
# rebind faster
|
sock = self._createListenSocket(socket.AF_INET6)
|
||||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
except socket.error, e:
|
||||||
sock.bind((HOST, PORT))
|
if (isinstance(e.args, tuple) and
|
||||||
sock.listen(2)
|
e.args[0] in (errno.EAFNOSUPPORT,
|
||||||
|
errno.EPFNOSUPPORT,
|
||||||
|
errno.ENOPROTOOPT)):
|
||||||
|
sock = self._createListenSocket(socket.AF_INET)
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
# regexp to match an IPv4-mapped IPv6 address
|
||||||
|
mappedAddressRegexp = re.compile(r'^::ffff:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$')
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
# We typically don't want to accept incoming connections if the user is using a
|
# We typically don't want to accept incoming connections if the user is using a
|
||||||
|
@ -56,18 +81,29 @@ class singleListener(threading.Thread):
|
||||||
print 'We are connected to too many people. Not accepting further incoming connections for ten seconds.'
|
print 'We are connected to too many people. Not accepting further incoming connections for ten seconds.'
|
||||||
|
|
||||||
time.sleep(10)
|
time.sleep(10)
|
||||||
a, (HOST, PORT) = sock.accept()
|
|
||||||
|
|
||||||
# The following code will, unfortunately, block an incoming
|
while True:
|
||||||
# connection if someone else on the same LAN is already connected
|
a, sockaddr = sock.accept()
|
||||||
# because the two computers will share the same external IP. This
|
(HOST, PORT) = sockaddr[0:2]
|
||||||
# is here to prevent connection flooding.
|
|
||||||
while HOST in shared.connectedHostsList:
|
# If the address is an IPv4-mapped IPv6 address then
|
||||||
|
# convert it to just the IPv4 representation
|
||||||
|
md = mappedAddressRegexp.match(HOST)
|
||||||
|
if md != None:
|
||||||
|
HOST = md.group(1)
|
||||||
|
|
||||||
|
# The following code will, unfortunately, block an
|
||||||
|
# incoming connection if someone else on the same LAN
|
||||||
|
# is already connected because the two computers will
|
||||||
|
# share the same external IP. This is here to prevent
|
||||||
|
# connection flooding.
|
||||||
|
if HOST in shared.connectedHostsList:
|
||||||
|
a.close()
|
||||||
with shared.printLock:
|
with shared.printLock:
|
||||||
print 'We are already connected to', HOST + '. Ignoring connection.'
|
print 'We are already connected to', HOST + '. Ignoring connection.'
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
a.close()
|
|
||||||
a, (HOST, PORT) = sock.accept()
|
|
||||||
someObjectsOfWhichThisRemoteNodeIsAlreadyAware = {} # This is not necessairly a complete list; we clear it from time to time to save memory.
|
someObjectsOfWhichThisRemoteNodeIsAlreadyAware = {} # This is not necessairly a complete list; we clear it from time to time to save memory.
|
||||||
sendDataThreadQueue = Queue.Queue() # Used to submit information to the send data thread for this connection.
|
sendDataThreadQueue = Queue.Queue() # Used to submit information to the send data thread for this connection.
|
||||||
a.settimeout(20)
|
a.settimeout(20)
|
||||||
|
|
|
@ -96,6 +96,13 @@ def isInSqlInventory(hash):
|
||||||
queryreturn = sqlQuery('''select hash from inventory where hash=?''', hash)
|
queryreturn = sqlQuery('''select hash from inventory where hash=?''', hash)
|
||||||
return queryreturn != []
|
return queryreturn != []
|
||||||
|
|
||||||
|
def encodeHost(host):
|
||||||
|
if host.find(':') == -1:
|
||||||
|
return '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' + \
|
||||||
|
socket.inet_aton(host)
|
||||||
|
else:
|
||||||
|
return socket.inet_pton(socket.AF_INET6, host)
|
||||||
|
|
||||||
def assembleVersionMessage(remoteHost, remotePort, myStreamNumber):
|
def assembleVersionMessage(remoteHost, remotePort, myStreamNumber):
|
||||||
payload = ''
|
payload = ''
|
||||||
payload += pack('>L', 2) # protocol version.
|
payload += pack('>L', 2) # protocol version.
|
||||||
|
@ -104,8 +111,7 @@ def assembleVersionMessage(remoteHost, remotePort, myStreamNumber):
|
||||||
|
|
||||||
payload += pack(
|
payload += pack(
|
||||||
'>q', 1) # boolservices of remote connection; ignored by the remote host.
|
'>q', 1) # boolservices of remote connection; ignored by the remote host.
|
||||||
payload += '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' + \
|
payload += encodeHost(remoteHost)
|
||||||
socket.inet_aton(remoteHost)
|
|
||||||
payload += pack('>H', remotePort) # remote IPv6 and port
|
payload += pack('>H', remotePort) # remote IPv6 and port
|
||||||
|
|
||||||
payload += pack('>q', 1) # bitflags of the services I offer.
|
payload += pack('>q', 1) # bitflags of the services I offer.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user