PyBitmessage/src/bitmessagemain.py
Neil Roberts 7da6ea958f 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.
2014-03-12 01:48:48 +00:00

255 lines
9.8 KiB
Python
Executable File

#!/usr/bin/env python
# Copyright (c) 2012 Jonathan Warren
# Copyright (c) 2012 The Bitmessage developers
# Distributed under the MIT/X11 software license. See the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
# Right now, PyBitmessage only support connecting to stream 1. It doesn't
# yet contain logic to expand into further streams.
# The software version variable is now held in shared.py
import signal # Used to capture a Ctrl-C keypress so that Bitmessage can shutdown gracefully.
# The next 3 are used for the API
import singleton
import os
import socket
import ctypes
from struct import pack
from SimpleXMLRPCServer import SimpleXMLRPCServer
from api import MySimpleXMLRPCRequestHandler
from helper_startup import isOurOperatingSystemLimitedToHavingVeryFewHalfOpenConnections
import shared
from helper_sql import sqlQuery
import threading
# Classes
#from helper_sql import *
#from class_sqlThread import *
from class_sqlThread import sqlThread
from class_singleCleaner import singleCleaner
#from class_singleWorker import *
from class_objectProcessor import objectProcessor
from class_outgoingSynSender import outgoingSynSender
from class_singleListener import singleListener
from class_singleWorker import singleWorker
#from class_addressGenerator import *
from class_addressGenerator import addressGenerator
from debug import logger
# Helper Functions
import helper_bootstrap
import helper_generic
from subprocess import call
import time
# OSX python version check
import sys
if sys.platform == 'darwin':
if float("{1}.{2}".format(*sys.version_info)) < 7.5:
msg = "You should use python 2.7.5 or greater. Your version: %s", "{0}.{1}.{2}".format(*sys.version_info)
logger.critical(msg)
print msg
sys.exit(0)
def connectToStream(streamNumber):
shared.streamsInWhichIAmParticipating[streamNumber] = 'no data'
selfInitiatedConnections[streamNumber] = {}
shared.inventorySets[streamNumber] = set()
queryData = sqlQuery('''SELECT hash FROM inventory WHERE streamnumber=?''', streamNumber)
for row in queryData:
shared.inventorySets[streamNumber].add(row[0])
if isOurOperatingSystemLimitedToHavingVeryFewHalfOpenConnections():
# Some XP and Vista systems can only have 10 outgoing connections at a time.
maximumNumberOfHalfOpenConnections = 9
else:
maximumNumberOfHalfOpenConnections = 64
for i in range(maximumNumberOfHalfOpenConnections):
a = outgoingSynSender()
a.setup(streamNumber, selfInitiatedConnections)
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.
class singleAPI(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
se = SimpleXMLRPCServer((shared.config.get('bitmessagesettings', 'apiinterface'), shared.config.getint(
'bitmessagesettings', 'apiport')), MySimpleXMLRPCRequestHandler, True, True)
se.register_introspection_functions()
se.serve_forever()
# This is a list of current connections (the thread pointers at least)
selfInitiatedConnections = {}
if shared.useVeryEasyProofOfWorkForTesting:
shared.networkDefaultProofOfWorkNonceTrialsPerByte = int(
shared.networkDefaultProofOfWorkNonceTrialsPerByte / 16)
shared.networkDefaultPayloadLengthExtraBytes = int(
shared.networkDefaultPayloadLengthExtraBytes / 7000)
class Main:
def start(self, daemon=False):
_fixWinsock()
shared.daemon = daemon
# is the application already running? If yes then exit.
thisapp = singleton.singleinstance()
signal.signal(signal.SIGINT, helper_generic.signal_handler)
# signal.signal(signal.SIGINT, signal.SIG_DFL)
helper_bootstrap.knownNodes()
# Start the address generation thread
addressGeneratorThread = addressGenerator()
addressGeneratorThread.daemon = True # close the main program even if there are threads left
addressGeneratorThread.start()
# Start the thread that calculates POWs
singleWorkerThread = singleWorker()
singleWorkerThread.daemon = True # close the main program even if there are threads left
singleWorkerThread.start()
# Start the SQL thread
sqlLookup = sqlThread()
sqlLookup.daemon = False # DON'T close the main program even if there are threads left. The closeEvent should command this thread to exit gracefully.
sqlLookup.start()
# Start the thread that calculates POWs
objectProcessorThread = objectProcessor()
objectProcessorThread.daemon = False # DON'T close the main program even the thread remains. This thread checks the shutdown variable after processing each object.
objectProcessorThread.start()
# Start the cleanerThread
singleCleanerThread = singleCleaner()
singleCleanerThread.daemon = True # close the main program even if there are threads left
singleCleanerThread.start()
shared.reloadMyAddressHashes()
shared.reloadBroadcastSendersForWhichImWatching()
if shared.safeConfigGetBoolean('bitmessagesettings', 'apienabled'):
try:
apiNotifyPath = shared.config.get(
'bitmessagesettings', 'apinotifypath')
except:
apiNotifyPath = ''
if apiNotifyPath != '':
with shared.printLock:
print 'Trying to call', apiNotifyPath
call([apiNotifyPath, "startingUp"])
singleAPIThread = singleAPI()
singleAPIThread.daemon = True # close the main program even if there are threads left
singleAPIThread.start()
connectToStream(1)
singleListenerThread = singleListener()
singleListenerThread.setup(selfInitiatedConnections)
singleListenerThread.daemon = True # close the main program even if there are threads left
singleListenerThread.start()
if daemon == False and shared.safeConfigGetBoolean('bitmessagesettings', 'daemon') == False:
try:
from PyQt4 import QtCore, QtGui
except Exception as err:
print 'PyBitmessage requires PyQt unless you want to run it as a daemon and interact with it using the API. You can download PyQt from http://www.riverbankcomputing.com/software/pyqt/download or by searching Google for \'PyQt Download\'. If you want to run in daemon mode, see https://bitmessage.org/wiki/Daemon'
print 'Error message:', err
os._exit(0)
import bitmessageqt
bitmessageqt.run()
else:
shared.config.remove_option('bitmessagesettings', 'dontconnect')
if daemon:
with shared.printLock:
print 'Running as a daemon. The main program should exit this thread.'
else:
with shared.printLock:
print 'Running as a daemon. You can use Ctrl+C to exit.'
while True:
time.sleep(20)
def stop(self):
with shared.printLock:
print 'Stopping Bitmessage Deamon.'
shared.doCleanShutdown()
#TODO: nice function but no one is using this
def getApiAddress(self):
if not shared.safeConfigGetBoolean('bitmessagesettings', 'apienabled'):
return None
address = shared.config.get('bitmessagesettings', 'apiinterface')
port = shared.config.getint('bitmessagesettings', 'apiport')
return {'address':address,'port':port}
if __name__ == "__main__":
mainprogram = Main()
mainprogram.start()
# So far, the creation of and management of the Bitmessage protocol and this
# client is a one-man operation. Bitcoin tips are quite appreciated.
# 1H5XaDA6fYENLbknwZyjiYXYPQaFjjLX2u