PyBitmessage-2021-04-27/src/class_singleListener.py
Peter Surda fd2603247d
Fix onionbindip for some systems with IPv6
- in some cases when IPv6 stack is available and onionbindip is an IPv4
  address, socket.bind doesn't change the bound address, ending up
  listening on everything
2017-05-10 20:01:23 +02:00

169 lines
7.9 KiB
Python

import threading
import shared
import socket
from bmconfigparser import BMConfigParser
from class_sendDataThread import *
from class_receiveDataThread import *
import helper_bootstrap
from helper_threading import *
import protocol
import errno
import re
import state
# Only one singleListener thread will ever exist. It creates the
# receiveDataThread and sendDataThread for each incoming connection. Note
# that it cannot set the stream number because it is not known yet- the
# other node will have to tell us its stream number in a version message.
# If we don't care about their stream, we will close the connection
# (within the recversion function of the recieveData thread)
class singleListener(threading.Thread, StoppableThread):
def __init__(self):
threading.Thread.__init__(self, name="singleListener")
self.initStop()
def setup(self, selfInitiatedConnections):
self.selfInitiatedConnections = selfInitiatedConnections
def _createListenSocket(self, family):
HOST = '' # Symbolic name meaning all available interfaces
# If not sockslisten, but onionhostname defined, only listen on localhost
if not BMConfigParser().safeGetBoolean('bitmessagesettings', 'sockslisten') and ".onion" in BMConfigParser().get('bitmessagesettings', 'onionhostname'):
if family == socket.AF_INET6 and "." in BMConfigParser().get('bitmessagesettings', 'onionbindip'):
raise socket.error(errno.EINVAL, "Invalid mix of IPv4 and IPv6")
elif family == socket.AF_INET and ":" in BMConfigParser().get('bitmessagesettings', 'onionbindip'):
raise socket.error(errno.EINVAL, "Invalid mix of IPv4 and IPv6")
HOST = BMConfigParser().get('bitmessagesettings', 'onionbindip')
PORT = BMConfigParser().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 stopThread(self):
super(singleListener, self).stopThread()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
for ip in ('127.0.0.1', BMConfigParser().get('bitmessagesettings', 'onionbindip')):
try:
s.connect((ip, BMConfigParser().getint('bitmessagesettings', 'port')))
s.shutdown(socket.SHUT_RDWR)
s.close()
break
except:
pass
def run(self):
# If there is a trusted peer then we don't want to accept
# incoming connections so we'll just abandon the thread
if state.trustedPeer:
return
while BMConfigParser().safeGetBoolean('bitmessagesettings', 'dontconnect') and state.shutdown == 0:
self.stop.wait(1)
helper_bootstrap.dns()
# We typically don't want to accept incoming connections if the user is using a
# SOCKS proxy, unless they have configured otherwise. If they eventually select
# proxy 'none' or configure SOCKS listening then this will start listening for
# connections. But if on SOCKS and have an onionhostname, listen
# (socket is then only opened for localhost)
while BMConfigParser().get('bitmessagesettings', 'socksproxytype')[0:5] == 'SOCKS' and \
(not BMConfigParser().getboolean('bitmessagesettings', 'sockslisten') and \
".onion" not in BMConfigParser().get('bitmessagesettings', 'onionhostname')) and \
state.shutdown == 0:
self.stop.wait(5)
logger.info('Listening for incoming connections.')
# First try listening on an IPv6 socket. This should also be
# able to accept connections on IPv4. If that's not available
# we'll fall back to IPv4-only.
try:
sock = self._createListenSocket(socket.AF_INET6)
except socket.error as e:
if (isinstance(e.args, tuple) and
e.args[0] in (errno.EAFNOSUPPORT,
errno.EPFNOSUPPORT,
errno.EADDRNOTAVAIL,
errno.ENOPROTOOPT,
errno.EINVAL)):
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 state.shutdown == 0:
# We typically don't want to accept incoming connections if the user is using a
# SOCKS proxy, unless they have configured otherwise. If they eventually select
# proxy 'none' or configure SOCKS listening then this will start listening for
# connections.
while BMConfigParser().get('bitmessagesettings', 'socksproxytype')[0:5] == 'SOCKS' and not BMConfigParser().getboolean('bitmessagesettings', 'sockslisten') and ".onion" not in BMConfigParser().get('bitmessagesettings', 'onionhostname') and state.shutdown == 0:
self.stop.wait(10)
while len(shared.connectedHostsList) > \
BMConfigParser().safeGetInt("bitmessagesettings", "maxtotalconnections", 200) + \
BMConfigParser().safeGetInt("bitmessagesettings", "maxbootstrapconnections", 20) \
and state.shutdown == 0:
logger.info('We are connected to too many people. Not accepting further incoming connections for ten seconds.')
self.stop.wait(10)
while state.shutdown == 0:
try:
socketObject, sockaddr = sock.accept()
except socket.error as e:
if isinstance(e.args, tuple) and \
e.args[0] in (errno.EINTR,):
continue
time.wait(1)
continue
(HOST, PORT) = sockaddr[0:2]
# 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.
# permit repeated connections from Tor
if HOST in shared.connectedHostsList and \
(".onion" not in BMConfigParser().get('bitmessagesettings', 'onionhostname') or not protocol.checkSocksIP(HOST)):
socketObject.close()
logger.info('We are already connected to ' + str(HOST) + '. Ignoring connection.')
else:
break
sendDataThreadQueue = Queue.Queue() # Used to submit information to the send data thread for this connection.
socketObject.settimeout(20)
sd = sendDataThread(sendDataThreadQueue)
sd.setup(
socketObject, HOST, PORT, -1)
sd.start()
rd = receiveDataThread()
rd.daemon = True # close the main program even if there are threads left
rd.setup(
socketObject, HOST, PORT, -1, self.selfInitiatedConnections, sendDataThreadQueue, sd.objectHashHolderInstance)
rd.start()
logger.info('connected to ' + HOST + ' during INCOMING request.')