From 0865e863ec3be131434a724d517c59647cf5f2b4 Mon Sep 17 00:00:00 2001 From: Jonathan Warren Date: Wed, 10 Sep 2014 16:47:51 -0400 Subject: [PATCH] Added ability to limit network transfer rate --- src/bitmessageqt/__init__.py | 54 ++++++++++++-------- src/bitmessageqt/settings.py | 90 +++++++++++++++++++++++----------- src/bitmessageqt/settings.ui | 72 ++++++++++++++++++++++++++- src/class_objectProcessor.py | 8 ++- src/class_outgoingSynSender.py | 57 ++++++++++++++------- src/class_receiveDataThread.py | 72 ++++++++++++++++++++++++--- src/class_sendDataThread.py | 29 +++++++++-- src/class_sqlThread.py | 2 + src/helper_startup.py | 2 + src/shared.py | 24 +++++++-- 10 files changed, 328 insertions(+), 82 deletions(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 2dcc04ca..ce3f0eff 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -1346,26 +1346,23 @@ class MyForm(QtGui.QMainWindow): if self.regenerateAddressesDialogInstance.ui.lineEditPassphrase.text() == "": QMessageBox.about(self, _translate("MainWindow", "bad passphrase"), _translate( "MainWindow", "You must type your passphrase. If you don\'t have one then this is not the form for you.")) - else: - streamNumberForAddress = int( - self.regenerateAddressesDialogInstance.ui.lineEditStreamNumber.text()) - try: - addressVersionNumber = int( - self.regenerateAddressesDialogInstance.ui.lineEditAddressVersionNumber.text()) - except: - QMessageBox.about(self, _translate("MainWindow", "Bad address version number"), _translate( - "MainWindow", "Your address version number must be a number: either 3 or 4.")) - if addressVersionNumber < 3 or addressVersionNumber > 4: - QMessageBox.about(self, _translate("MainWindow", "Bad address version number"), _translate( - "MainWindow", "Your address version number must be either 3 or 4.")) - # self.addressGenerator = addressGenerator() - # self.addressGenerator.setup(addressVersionNumber,streamNumberForAddress,"unused address",self.regenerateAddressesDialogInstance.ui.spinBoxNumberOfAddressesToMake.value(),self.regenerateAddressesDialogInstance.ui.lineEditPassphrase.text().toUtf8(),self.regenerateAddressesDialogInstance.ui.checkBoxEighteenByteRipe.isChecked()) - # QtCore.QObject.connect(self.addressGenerator, SIGNAL("writeNewAddressToTable(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), self.writeNewAddressToTable) - # QtCore.QObject.connect(self.addressGenerator, QtCore.SIGNAL("updateStatusBar(PyQt_PyObject)"), self.updateStatusBar) - # self.addressGenerator.start() - shared.addressGeneratorQueue.put(('createDeterministicAddresses', addressVersionNumber, streamNumberForAddress, "regenerated deterministic address", self.regenerateAddressesDialogInstance.ui.spinBoxNumberOfAddressesToMake.value( - ), self.regenerateAddressesDialogInstance.ui.lineEditPassphrase.text().toUtf8(), self.regenerateAddressesDialogInstance.ui.checkBoxEighteenByteRipe.isChecked())) - self.ui.tabWidget.setCurrentIndex(3) + return + streamNumberForAddress = int( + self.regenerateAddressesDialogInstance.ui.lineEditStreamNumber.text()) + try: + addressVersionNumber = int( + self.regenerateAddressesDialogInstance.ui.lineEditAddressVersionNumber.text()) + except: + QMessageBox.about(self, _translate("MainWindow", "Bad address version number"), _translate( + "MainWindow", "Your address version number must be a number: either 3 or 4.")) + return + if addressVersionNumber < 3 or addressVersionNumber > 4: + QMessageBox.about(self, _translate("MainWindow", "Bad address version number"), _translate( + "MainWindow", "Your address version number must be either 3 or 4.")) + return + shared.addressGeneratorQueue.put(('createDeterministicAddresses', addressVersionNumber, streamNumberForAddress, "regenerated deterministic address", self.regenerateAddressesDialogInstance.ui.spinBoxNumberOfAddressesToMake.value( + ), self.regenerateAddressesDialogInstance.ui.lineEditPassphrase.text().toUtf8(), self.regenerateAddressesDialogInstance.ui.checkBoxEighteenByteRipe.isChecked())) + self.ui.tabWidget.setCurrentIndex(3) def click_actionJoinChan(self): self.newChanDialogInstance = newChanDialog(self) @@ -2322,6 +2319,15 @@ class MyForm(QtGui.QMainWindow): self.settingsDialogInstance.ui.lineEditSocksPassword.text())) shared.config.set('bitmessagesettings', 'sockslisten', str( self.settingsDialogInstance.ui.checkBoxSocksListen.isChecked())) + try: + # Rounding to integers just for aesthetics + shared.config.set('bitmessagesettings', 'maxdownloadrate', str( + int(float(self.settingsDialogInstance.ui.lineEditMaxDownloadRate.text())))) + shared.config.set('bitmessagesettings', 'maxuploadrate', str( + int(float(self.settingsDialogInstance.ui.lineEditMaxUploadRate.text())))) + except: + QMessageBox.about(self, _translate("MainWindow", "Number needed"), _translate( + "MainWindow", "Your maximum download and upload rate must be numbers. Ignoring what you typed.")) shared.config.set('bitmessagesettings', 'namecoinrpctype', self.settingsDialogInstance.getNamecoinType()) @@ -2333,7 +2339,8 @@ class MyForm(QtGui.QMainWindow): self.settingsDialogInstance.ui.lineEditNamecoinUser.text())) shared.config.set('bitmessagesettings', 'namecoinrpcpassword', str( self.settingsDialogInstance.ui.lineEditNamecoinPassword.text())) - + + # Demanded difficulty tab if float(self.settingsDialogInstance.ui.lineEditTotalDifficulty.text()) >= 1: shared.config.set('bitmessagesettings', 'defaultnoncetrialsperbyte', str(int(float( self.settingsDialogInstance.ui.lineEditTotalDifficulty.text()) * shared.networkDefaultProofOfWorkNonceTrialsPerByte))) @@ -3443,7 +3450,12 @@ class settingsDialog(QtGui.QDialog): shared.config.get('bitmessagesettings', 'sockspassword'))) QtCore.QObject.connect(self.ui.comboBoxProxyType, QtCore.SIGNAL( "currentIndexChanged(int)"), self.comboBoxProxyTypeChanged) + self.ui.lineEditMaxDownloadRate.setText(str( + shared.config.get('bitmessagesettings', 'maxdownloadrate'))) + self.ui.lineEditMaxUploadRate.setText(str( + shared.config.get('bitmessagesettings', 'maxuploadrate'))) + # Demanded difficulty tab self.ui.lineEditTotalDifficulty.setText(str((float(shared.config.getint( 'bitmessagesettings', 'defaultnoncetrialsperbyte')) / shared.networkDefaultProofOfWorkNonceTrialsPerByte))) self.ui.lineEditSmallMessageDifficulty.setText(str((float(shared.config.getint( diff --git a/src/bitmessageqt/settings.py b/src/bitmessageqt/settings.py index d4d2fd3f..40de6a5a 100644 --- a/src/bitmessageqt/settings.py +++ b/src/bitmessageqt/settings.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'settings.ui' # -# Created: Mon May 19 15:54:27 2014 +# Created: Tue Sep 09 15:13:28 2014 # by: PyQt4 UI code generator 4.10.3 # # WARNING! All changes made in this file will be lost! @@ -128,6 +128,37 @@ class Ui_settingsDialog(object): self.lineEditTCPPort.setObjectName(_fromUtf8("lineEditTCPPort")) self.gridLayout_3.addWidget(self.lineEditTCPPort, 0, 2, 1, 1) self.gridLayout_4.addWidget(self.groupBox1, 0, 0, 1, 1) + self.groupBox_3 = QtGui.QGroupBox(self.tabNetworkSettings) + self.groupBox_3.setObjectName(_fromUtf8("groupBox_3")) + self.gridLayout_9 = QtGui.QGridLayout(self.groupBox_3) + self.gridLayout_9.setObjectName(_fromUtf8("gridLayout_9")) + spacerItem1 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_9.addItem(spacerItem1, 0, 0, 2, 1) + self.label_24 = QtGui.QLabel(self.groupBox_3) + self.label_24.setObjectName(_fromUtf8("label_24")) + self.gridLayout_9.addWidget(self.label_24, 0, 1, 1, 1) + self.lineEditMaxDownloadRate = QtGui.QLineEdit(self.groupBox_3) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lineEditMaxDownloadRate.sizePolicy().hasHeightForWidth()) + self.lineEditMaxDownloadRate.setSizePolicy(sizePolicy) + self.lineEditMaxDownloadRate.setMaximumSize(QtCore.QSize(60, 16777215)) + self.lineEditMaxDownloadRate.setObjectName(_fromUtf8("lineEditMaxDownloadRate")) + self.gridLayout_9.addWidget(self.lineEditMaxDownloadRate, 0, 2, 1, 1) + self.label_25 = QtGui.QLabel(self.groupBox_3) + self.label_25.setObjectName(_fromUtf8("label_25")) + self.gridLayout_9.addWidget(self.label_25, 1, 1, 1, 1) + self.lineEditMaxUploadRate = QtGui.QLineEdit(self.groupBox_3) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lineEditMaxUploadRate.sizePolicy().hasHeightForWidth()) + self.lineEditMaxUploadRate.setSizePolicy(sizePolicy) + self.lineEditMaxUploadRate.setMaximumSize(QtCore.QSize(60, 16777215)) + self.lineEditMaxUploadRate.setObjectName(_fromUtf8("lineEditMaxUploadRate")) + self.gridLayout_9.addWidget(self.lineEditMaxUploadRate, 1, 2, 1, 1) + self.gridLayout_4.addWidget(self.groupBox_3, 2, 0, 1, 1) self.groupBox_2 = QtGui.QGroupBox(self.tabNetworkSettings) self.groupBox_2.setObjectName(_fromUtf8("groupBox_2")) self.gridLayout_2 = QtGui.QGridLayout(self.groupBox_2) @@ -176,8 +207,8 @@ class Ui_settingsDialog(object): self.comboBoxProxyType.addItem(_fromUtf8("")) self.gridLayout_2.addWidget(self.comboBoxProxyType, 0, 1, 1, 1) self.gridLayout_4.addWidget(self.groupBox_2, 1, 0, 1, 1) - spacerItem1 = QtGui.QSpacerItem(20, 70, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) - self.gridLayout_4.addItem(spacerItem1, 2, 0, 1, 1) + spacerItem2 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + self.gridLayout_4.addItem(spacerItem2, 3, 0, 1, 1) self.tabWidgetSettings.addTab(self.tabNetworkSettings, _fromUtf8("")) self.tabDemandedDifficulty = QtGui.QWidget() self.tabDemandedDifficulty.setObjectName(_fromUtf8("tabDemandedDifficulty")) @@ -199,8 +230,8 @@ class Ui_settingsDialog(object): self.label_8.setWordWrap(True) self.label_8.setObjectName(_fromUtf8("label_8")) self.gridLayout_6.addWidget(self.label_8, 0, 0, 1, 3) - spacerItem2 = QtGui.QSpacerItem(203, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_6.addItem(spacerItem2, 1, 0, 1, 1) + spacerItem3 = QtGui.QSpacerItem(203, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_6.addItem(spacerItem3, 1, 0, 1, 1) self.label_12 = QtGui.QLabel(self.tabDemandedDifficulty) self.label_12.setWordWrap(True) self.label_12.setObjectName(_fromUtf8("label_12")) @@ -223,10 +254,10 @@ class Ui_settingsDialog(object): self.lineEditTotalDifficulty.setMaximumSize(QtCore.QSize(70, 16777215)) self.lineEditTotalDifficulty.setObjectName(_fromUtf8("lineEditTotalDifficulty")) self.gridLayout_6.addWidget(self.lineEditTotalDifficulty, 1, 2, 1, 1) - spacerItem3 = QtGui.QSpacerItem(203, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_6.addItem(spacerItem3, 3, 0, 1, 1) - spacerItem4 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) - self.gridLayout_6.addItem(spacerItem4, 5, 0, 1, 1) + spacerItem4 = QtGui.QSpacerItem(203, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_6.addItem(spacerItem4, 3, 0, 1, 1) + spacerItem5 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + self.gridLayout_6.addItem(spacerItem5, 5, 0, 1, 1) self.tabWidgetSettings.addTab(self.tabDemandedDifficulty, _fromUtf8("")) self.tabMaxAcceptableDifficulty = QtGui.QWidget() self.tabMaxAcceptableDifficulty.setObjectName(_fromUtf8("tabMaxAcceptableDifficulty")) @@ -236,8 +267,8 @@ class Ui_settingsDialog(object): self.label_15.setWordWrap(True) self.label_15.setObjectName(_fromUtf8("label_15")) self.gridLayout_7.addWidget(self.label_15, 0, 0, 1, 3) - spacerItem5 = QtGui.QSpacerItem(102, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_7.addItem(spacerItem5, 1, 0, 1, 1) + spacerItem6 = QtGui.QSpacerItem(102, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_7.addItem(spacerItem6, 1, 0, 1, 1) self.label_13 = QtGui.QLabel(self.tabMaxAcceptableDifficulty) self.label_13.setLayoutDirection(QtCore.Qt.LeftToRight) self.label_13.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) @@ -252,8 +283,8 @@ class Ui_settingsDialog(object): self.lineEditMaxAcceptableTotalDifficulty.setMaximumSize(QtCore.QSize(70, 16777215)) self.lineEditMaxAcceptableTotalDifficulty.setObjectName(_fromUtf8("lineEditMaxAcceptableTotalDifficulty")) self.gridLayout_7.addWidget(self.lineEditMaxAcceptableTotalDifficulty, 1, 2, 1, 1) - spacerItem6 = QtGui.QSpacerItem(102, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_7.addItem(spacerItem6, 2, 0, 1, 1) + spacerItem7 = QtGui.QSpacerItem(102, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_7.addItem(spacerItem7, 2, 0, 1, 1) self.label_14 = QtGui.QLabel(self.tabMaxAcceptableDifficulty) self.label_14.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.label_14.setObjectName(_fromUtf8("label_14")) @@ -267,15 +298,15 @@ class Ui_settingsDialog(object): self.lineEditMaxAcceptableSmallMessageDifficulty.setMaximumSize(QtCore.QSize(70, 16777215)) self.lineEditMaxAcceptableSmallMessageDifficulty.setObjectName(_fromUtf8("lineEditMaxAcceptableSmallMessageDifficulty")) self.gridLayout_7.addWidget(self.lineEditMaxAcceptableSmallMessageDifficulty, 2, 2, 1, 1) - spacerItem7 = QtGui.QSpacerItem(20, 147, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) - self.gridLayout_7.addItem(spacerItem7, 3, 1, 1, 1) + spacerItem8 = QtGui.QSpacerItem(20, 147, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + self.gridLayout_7.addItem(spacerItem8, 3, 1, 1, 1) self.tabWidgetSettings.addTab(self.tabMaxAcceptableDifficulty, _fromUtf8("")) self.tabNamecoin = QtGui.QWidget() self.tabNamecoin.setObjectName(_fromUtf8("tabNamecoin")) self.gridLayout_8 = QtGui.QGridLayout(self.tabNamecoin) self.gridLayout_8.setObjectName(_fromUtf8("gridLayout_8")) - spacerItem8 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_8.addItem(spacerItem8, 2, 0, 1, 1) + spacerItem9 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_8.addItem(spacerItem9, 2, 0, 1, 1) self.label_16 = QtGui.QLabel(self.tabNamecoin) self.label_16.setWordWrap(True) self.label_16.setObjectName(_fromUtf8("label_16")) @@ -287,10 +318,10 @@ class Ui_settingsDialog(object): self.lineEditNamecoinHost = QtGui.QLineEdit(self.tabNamecoin) self.lineEditNamecoinHost.setObjectName(_fromUtf8("lineEditNamecoinHost")) self.gridLayout_8.addWidget(self.lineEditNamecoinHost, 2, 2, 1, 1) - spacerItem9 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_8.addItem(spacerItem9, 3, 0, 1, 1) spacerItem10 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_8.addItem(spacerItem10, 4, 0, 1, 1) + self.gridLayout_8.addItem(spacerItem10, 3, 0, 1, 1) + spacerItem11 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_8.addItem(spacerItem11, 4, 0, 1, 1) self.label_18 = QtGui.QLabel(self.tabNamecoin) self.label_18.setEnabled(True) self.label_18.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) @@ -299,8 +330,8 @@ class Ui_settingsDialog(object): self.lineEditNamecoinPort = QtGui.QLineEdit(self.tabNamecoin) self.lineEditNamecoinPort.setObjectName(_fromUtf8("lineEditNamecoinPort")) self.gridLayout_8.addWidget(self.lineEditNamecoinPort, 3, 2, 1, 1) - spacerItem11 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) - self.gridLayout_8.addItem(spacerItem11, 8, 1, 1, 1) + spacerItem12 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + self.gridLayout_8.addItem(spacerItem12, 8, 1, 1, 1) self.labelNamecoinUser = QtGui.QLabel(self.tabNamecoin) self.labelNamecoinUser.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.labelNamecoinUser.setObjectName(_fromUtf8("labelNamecoinUser")) @@ -308,8 +339,8 @@ class Ui_settingsDialog(object): self.lineEditNamecoinUser = QtGui.QLineEdit(self.tabNamecoin) self.lineEditNamecoinUser.setObjectName(_fromUtf8("lineEditNamecoinUser")) self.gridLayout_8.addWidget(self.lineEditNamecoinUser, 4, 2, 1, 1) - spacerItem12 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_8.addItem(spacerItem12, 5, 0, 1, 1) + spacerItem13 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_8.addItem(spacerItem13, 5, 0, 1, 1) self.labelNamecoinPassword = QtGui.QLabel(self.tabNamecoin) self.labelNamecoinPassword.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.labelNamecoinPassword.setObjectName(_fromUtf8("labelNamecoinPassword")) @@ -347,8 +378,8 @@ class Ui_settingsDialog(object): self.label_7.setWordWrap(True) self.label_7.setObjectName(_fromUtf8("label_7")) self.gridLayout_5.addWidget(self.label_7, 0, 0, 1, 3) - spacerItem13 = QtGui.QSpacerItem(212, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_5.addItem(spacerItem13, 1, 0, 1, 1) + spacerItem14 = QtGui.QSpacerItem(212, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_5.addItem(spacerItem14, 1, 0, 1, 1) self.widget = QtGui.QWidget(self.tabResendsExpire) self.widget.setMinimumSize(QtCore.QSize(231, 75)) self.widget.setObjectName(_fromUtf8("widget")) @@ -373,8 +404,8 @@ class Ui_settingsDialog(object): self.label_23.setGeometry(QtCore.QRect(170, 41, 71, 16)) self.label_23.setObjectName(_fromUtf8("label_23")) self.gridLayout_5.addWidget(self.widget, 1, 2, 1, 1) - spacerItem14 = QtGui.QSpacerItem(20, 129, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) - self.gridLayout_5.addItem(spacerItem14, 2, 1, 1, 1) + spacerItem15 = QtGui.QSpacerItem(20, 129, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + self.gridLayout_5.addItem(spacerItem15, 2, 1, 1, 1) self.tabWidgetSettings.addTab(self.tabResendsExpire, _fromUtf8("")) self.gridLayout.addWidget(self.tabWidgetSettings, 0, 0, 1, 1) @@ -416,6 +447,9 @@ class Ui_settingsDialog(object): self.tabWidgetSettings.setTabText(self.tabWidgetSettings.indexOf(self.tabUserInterface), _translate("settingsDialog", "User Interface", None)) self.groupBox1.setTitle(_translate("settingsDialog", "Listening port", None)) self.label.setText(_translate("settingsDialog", "Listen for connections on port:", None)) + self.groupBox_3.setTitle(_translate("settingsDialog", "Bandwidth limit", None)) + self.label_24.setText(_translate("settingsDialog", "Maximum download rate (kB/s): [0: unlimited]", None)) + self.label_25.setText(_translate("settingsDialog", "Maximum upload rate (kB/s): [0: unlimited]", None)) self.groupBox_2.setTitle(_translate("settingsDialog", "Proxy server / Tor", None)) self.label_2.setText(_translate("settingsDialog", "Type:", None)) self.label_3.setText(_translate("settingsDialog", "Server hostname:", None)) diff --git a/src/bitmessageqt/settings.ui b/src/bitmessageqt/settings.ui index 5ef3f0ba..14749d5e 100644 --- a/src/bitmessageqt/settings.ui +++ b/src/bitmessageqt/settings.ui @@ -247,6 +247,74 @@ + + + + Bandwidth limit + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Maximum download rate (kB/s): [0: unlimited] + + + + + + + + 0 + 0 + + + + + 60 + 16777215 + + + + + + + + Maximum upload rate (kB/s): [0: unlimited] + + + + + + + + 0 + 0 + + + + + 60 + 16777215 + + + + + + + @@ -350,7 +418,7 @@ - + Qt::Vertical @@ -358,7 +426,7 @@ 20 - 70 + 40 diff --git a/src/class_objectProcessor.py b/src/class_objectProcessor.py index d8d65559..73572c34 100644 --- a/src/class_objectProcessor.py +++ b/src/class_objectProcessor.py @@ -1024,7 +1024,13 @@ class objectProcessor(threading.Thread): if len(payload) != payloadLength: logger.info('ackData payload length doesn\'t match the payload length specified in the header. Not sending ackdata.') return False - if payloadLength > 2 ** 18: # 256 KiB + if payloadLength > 1600100: # ~1.6 MB which is the maximum possible size of an inv message. + """ + The largest message should be either an inv or a getdata message at 1.6 MB in size. + That doesn't mean that the object may be that big. The + shared.checkAndShareObjectWithPeers function will verify that it is no larger than + 2^18 bytes. + """ return False if checksum != hashlib.sha512(payload).digest()[0:4]: # test the checksum in the message. logger.info('ackdata checksum wrong. Not sending ackdata.') diff --git a/src/class_outgoingSynSender.py b/src/class_outgoingSynSender.py index a1a04b51..453acfe1 100644 --- a/src/class_outgoingSynSender.py +++ b/src/class_outgoingSynSender.py @@ -64,8 +64,6 @@ class outgoingSynSender(threading.Thread): shared.alreadyAttemptedConnectionsListLock.acquire() shared.alreadyAttemptedConnectionsList[peer] = 0 shared.alreadyAttemptedConnectionsListLock.release() - timeNodeLastSeen = shared.knownNodes[ - self.streamNumber][peer] if peer.host.find(':') == -1: address_family = socket.AF_INET else: @@ -83,7 +81,10 @@ class outgoingSynSender(threading.Thread): So let us remove the offending address from our knownNodes file. """ shared.knownNodesLock.acquire() - del shared.knownNodes[self.streamNumber][peer] + try: + del shared.knownNodes[self.streamNumber][peer] + except: + pass shared.knownNodesLock.release() with shared.printLock: print 'deleting ', peer, 'from shared.knownNodes because it caused a socks.socksocket exception. We must not be 64-bit compatible.' @@ -169,14 +170,24 @@ class outgoingSynSender(threading.Thread): with shared.printLock: print 'Could NOT connect to', peer, 'during outgoing attempt.', err - timeLastSeen = shared.knownNodes[ - self.streamNumber][peer] - if (int(time.time()) - timeLastSeen) > 172800 and len(shared.knownNodes[self.streamNumber]) > 1000: # for nodes older than 48 hours old if we have more than 1000 hosts in our list, delete from the shared.knownNodes data-structure. - shared.knownNodesLock.acquire() - del shared.knownNodes[self.streamNumber][peer] - shared.knownNodesLock.release() + deletedPeer = None + with shared.knownNodesLock: + """ + It is remotely possible that peer is no longer in shared.knownNodes. + This could happen if two outgoingSynSender threads both try to + connect to the same peer, both fail, and then both try to remove + it from shared.knownNodes. This is unlikely because of the + alreadyAttemptedConnectionsList but because we clear that list once + every half hour, it can happen. + """ + if peer in shared.knownNodes[self.streamNumber]: + timeLastSeen = shared.knownNodes[self.streamNumber][peer] + if (int(time.time()) - timeLastSeen) > 172800 and len(shared.knownNodes[self.streamNumber]) > 1000: # for nodes older than 48 hours old if we have more than 1000 hosts in our list, delete from the shared.knownNodes data-structure. + del shared.knownNodes[self.streamNumber][peer] + deletedPeer = peer + if deletedPeer: with shared.printLock: - print 'deleting ', peer, 'from shared.knownNodes because it is more than 48 hours old and we could not connect to it.' + print 'deleting', peer, 'from shared.knownNodes because it is more than 48 hours old and we could not connect to it.' except socks.Socks5AuthError as err: shared.UISignalQueue.put(( @@ -195,14 +206,24 @@ class outgoingSynSender(threading.Thread): with shared.printLock: print 'Could NOT connect to', peer, 'during outgoing attempt.', err - timeLastSeen = shared.knownNodes[ - self.streamNumber][peer] - if (int(time.time()) - timeLastSeen) > 172800 and len(shared.knownNodes[self.streamNumber]) > 1000: # for nodes older than 48 hours old if we have more than 1000 hosts in our list, delete from the knownNodes data-structure. - shared.knownNodesLock.acquire() - del shared.knownNodes[self.streamNumber][peer] - shared.knownNodesLock.release() - with shared.printLock: - print 'deleting ', peer, 'from knownNodes because it is more than 48 hours old and we could not connect to it.' + deletedPeer = None + with shared.knownNodesLock: + """ + It is remotely possible that peer is no longer in shared.knownNodes. + This could happen if two outgoingSynSender threads both try to + connect to the same peer, both fail, and then both try to remove + it from shared.knownNodes. This is unlikely because of the + alreadyAttemptedConnectionsList but because we clear that list once + every half hour, it can happen. + """ + if peer in shared.knownNodes[self.streamNumber]: + timeLastSeen = shared.knownNodes[self.streamNumber][peer] + if (int(time.time()) - timeLastSeen) > 172800 and len(shared.knownNodes[self.streamNumber]) > 1000: # for nodes older than 48 hours old if we have more than 1000 hosts in our list, delete from the shared.knownNodes data-structure. + del shared.knownNodes[self.streamNumber][peer] + deletedPeer = peer + if deletedPeer: + with shared.printLock: + print 'deleting', peer, 'from shared.knownNodes because it is more than 48 hours old and we could not connect to it.' except Exception as err: sys.stderr.write( diff --git a/src/class_receiveDataThread.py b/src/class_receiveDataThread.py index 2dfc8135..f53aec1e 100644 --- a/src/class_receiveDataThread.py +++ b/src/class_receiveDataThread.py @@ -65,11 +65,25 @@ class receiveDataThread(threading.Thread): print 'receiveDataThread starting. ID', str(id(self)) + '. The size of the shared.connectedHostsList is now', len(shared.connectedHostsList) while True: + if shared.config.getint('bitmessagesettings', 'maxdownloadrate') == 0: + downloadRateLimitBytes = float("inf") + else: + downloadRateLimitBytes = shared.config.getint('bitmessagesettings', 'maxdownloadrate') * 1000 + with shared.receiveDataLock: + while shared.numberOfBytesReceivedLastSecond >= downloadRateLimitBytes: + if int(time.time()) == shared.lastTimeWeResetBytesReceived: + # If it's still the same second that it was last time then sleep. + time.sleep(0.3) + else: + # It's a new second. Let us clear the shared.numberOfBytesReceivedLastSecond. + shared.lastTimeWeResetBytesReceived = int(time.time()) + shared.numberOfBytesReceivedLastSecond = 0 dataLen = len(self.data) try: - dataRecv = self.sock.recv(4096) + dataRecv = self.sock.recv(1024) self.data += dataRecv - shared.numberOfBytesReceived += len(dataRecv) + shared.numberOfBytesReceived += len(dataRecv) # for the 'network status' UI tab. The UI clears this value whenever it updates. + shared.numberOfBytesReceivedLastSecond += len(dataRecv) # for the download rate limit except socket.timeout: with shared.printLock: print 'Timeout occurred waiting for data from', self.peer, '. Closing receiveData thread. (ID:', str(id(self)) + ')' @@ -117,7 +131,7 @@ class receiveDataThread(threading.Thread): if magic != 0xE9BEB4D9: self.data = "" return - if payloadLength > 2 ** 18: # 256 KiB + if payloadLength > 1600100: # ~1.6 MB which is the maximum possible size of an inv message. logger.info('The incoming message, which we have not yet download, is too large. Ignoring it. (unfortunately there is no way to tell the other node to stop sending it except to disconnect.) Message size: %s' % payloadLength) self.data = self.data[payloadLength + shared.Header.size:] del magic,command,payloadLength,checksum # we don't need these anymore and better to clean them now before the recursive call rather than after @@ -148,7 +162,9 @@ class receiveDataThread(threading.Thread): try: #TODO: Use a dispatcher here - if not self.connectionIsOrWasFullyEstablished: + if command == 'error': + self.recerror(payload) + elif not self.connectionIsOrWasFullyEstablished: if command == 'version': self.recversion(payload) elif command == 'verack': @@ -321,6 +337,37 @@ class receiveDataThread(threading.Thread): with shared.printLock: print 'Timing attack mitigation: Sleeping for', sleepTime, 'seconds.' time.sleep(sleepTime) + + def recerror(self, data): + """ + The remote node has been polite enough to send you an error message. + """ + fatalStatus, readPosition = decodeVarint(data[:10]) + banTime, banTimeLength = decodeVarint(data[readPosition:readPosition+10]) + readPosition += banTimeLength + inventoryVectorLength, inventoryVectorLengthLength = decodeVarint(data[readPosition:readPosition+10]) + if inventoryVectorLength > 100: + return + readPosition += inventoryVectorLengthLength + inventoryVector = data[readPosition:readPosition+inventoryVectorLength] + readPosition += inventoryVectorLength + errorTextLength, errorTextLengthLength = decodeVarint(data[readPosition:readPosition+10]) + if errorTextLength > 1000: + return + readPosition += errorTextLengthLength + errorText = data[readPosition:readPosition+errorTextLength] + if fatalStatus == 0: + fatalHumanFriendly = 'Warning' + elif fatalStatus == 1: + fatalHumanFriendly = 'Error' + elif fatalStatus == 2: + fatalHumanFriendly = 'Fatal' + message = '%s message received from %s: %s.' % (fatalHumanFriendly, self.peer, errorText) + if inventoryVector: + message += " This concerns object %s" % inventoryVector.encode('hex') + if banTime > 0: + message += " Remote node says that the ban time is %s" % banTime + logger.error(message) def recobject(self, data): @@ -672,7 +719,20 @@ class receiveDataThread(threading.Thread): with shared.printLock: print 'Closing connection to old protocol version', self.remoteProtocolVersion, 'node: ', self.peer return - # print 'remoteProtocolVersion', self.remoteProtocolVersion + timestamp, = unpack('>Q', data[12:20]) + timeOffset = timestamp - int(time.time()) + if timeOffset > 3600: + self.sendDataThreadQueue.put((0, 'sendRawData', shared.assembleErrorMessage(fatal=2, errorText="Your time is too far in the future compared to mine. Closing connection."))) + logger.info("%s's time is too far in the future (%s seconds). Closing connection to it." % (self.peer, timeOffset)) + time.sleep(2) + self.sendDataThreadQueue.put((0, 'shutdown','no data')) + return + if timeOffset < -3600: + self.sendDataThreadQueue.put((0, 'sendRawData', shared.assembleErrorMessage(fatal=2, errorText="Your time is too far in the past compared to mine. Closing connection."))) + logger.info("%s's time is too far in the past (timeOffset %s seconds). Closing connection to it." % (self.peer, timeOffset)) + time.sleep(2) + self.sendDataThreadQueue.put((0, 'shutdown','no data')) + return self.myExternalIP = socket.inet_ntoa(data[40:44]) # print 'myExternalIP', self.myExternalIP self.remoteNodeIncomingPort, = unpack('>H', data[70:72]) @@ -688,7 +748,7 @@ class receiveDataThread(threading.Thread): self.streamNumber, lengthOfRemoteStreamNumber = decodeVarint( data[readPosition:]) with shared.printLock: - print 'Remote node useragent:', useragent, ' stream number:', self.streamNumber + print 'Remote node useragent:', useragent, ' stream number:', self.streamNumber, ' time offset:', timeOffset, 'seconds.' if self.streamNumber != 1: self.sendDataThreadQueue.put((0, 'shutdown','no data')) diff --git a/src/class_sendDataThread.py b/src/class_sendDataThread.py index 1d847113..742b1b95 100644 --- a/src/class_sendDataThread.py +++ b/src/class_sendDataThread.py @@ -62,9 +62,32 @@ class sendDataThread(threading.Thread): self.versionSent = 1 def sendBytes(self, data): - self.sock.sendall(data) - shared.numberOfBytesSent += len(data) - self.lastTimeISentData = int(time.time()) + if shared.config.getint('bitmessagesettings', 'maxuploadrate') == 0: + uploadRateLimitBytes = 999999999 # float("inf") doesn't work + else: + uploadRateLimitBytes = shared.config.getint('bitmessagesettings', 'maxuploadrate') * 1000 + with shared.sendDataLock: + while data: + while shared.numberOfBytesSentLastSecond >= uploadRateLimitBytes: + if int(time.time()) == shared.lastTimeWeResetBytesSent: + time.sleep(0.3) + else: + # It's a new second. Let us clear the shared.numberOfBytesSentLastSecond + shared.lastTimeWeResetBytesSent = int(time.time()) + shared.numberOfBytesSentLastSecond = 0 + # If the user raises or lowers the uploadRateLimit then we should make use of + # the new setting. If we are hitting the limit then we'll check here about + # once per second. + if shared.config.getint('bitmessagesettings', 'maxuploadrate') == 0: + uploadRateLimitBytes = 999999999 # float("inf") doesn't work + else: + uploadRateLimitBytes = shared.config.getint('bitmessagesettings', 'maxuploadrate') * 1000 + numberOfBytesWeMaySend = uploadRateLimitBytes - shared.numberOfBytesSentLastSecond + self.sock.sendall(data[:numberOfBytesWeMaySend]) + shared.numberOfBytesSent += len(data[:numberOfBytesWeMaySend]) # used for the 'network status' tab in the UI + shared.numberOfBytesSentLastSecond += len(data[:numberOfBytesWeMaySend]) + self.lastTimeISentData = int(time.time()) + data = data[numberOfBytesWeMaySend:] def run(self): diff --git a/src/class_sqlThread.py b/src/class_sqlThread.py index 24519e63..26c08154 100644 --- a/src/class_sqlThread.py +++ b/src/class_sqlThread.py @@ -341,6 +341,8 @@ class sqlThread(threading.Thread): shared.config.set(addressInKeysFile,'payloadlengthextrabytes', str(int(previousSmallMessageDifficulty * 1000))) except: continue + shared.config.set('bitmessagesettings', 'maxdownloadrate', '0') + shared.config.set('bitmessagesettings', 'maxuploadrate', '0') shared.config.set('bitmessagesettings', 'settingsversion', '10') with open(shared.appdata + 'keys.dat', 'wb') as configfile: shared.config.write(configfile) diff --git a/src/helper_startup.py b/src/helper_startup.py index 0f07377e..d192070f 100644 --- a/src/helper_startup.py +++ b/src/helper_startup.py @@ -102,6 +102,8 @@ def loadConfig(): shared.config.set('bitmessagesettings', 'useidenticons', 'True') shared.config.set('bitmessagesettings', 'identiconsuffix', ''.join(random.choice("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") for x in range(12))) # a twelve character pseudo-password to salt the identicons shared.config.set('bitmessagesettings', 'replybelow', 'False') + shared.config.set('bitmessagesettings', 'maxdownloadrate', '0') + shared.config.set('bitmessagesettings', 'maxuploadrate', '0') #start:UI setting to stop trying to send messages after X days/months shared.config.set( diff --git a/src/shared.py b/src/shared.py index 41ad80d4..7acf5e2c 100644 --- a/src/shared.py +++ b/src/shared.py @@ -70,8 +70,14 @@ numberOfMessagesProcessed = 0 numberOfBroadcastsProcessed = 0 numberOfPubkeysProcessed = 0 numberOfInventoryLookupsPerformed = 0 -numberOfBytesReceived = 0 -numberOfBytesSent = 0 +numberOfBytesReceived = 0 # Used for the 'network status' page +numberOfBytesSent = 0 # Used for the 'network status' page +numberOfBytesReceivedLastSecond = 0 # used for the bandwidth rate limit +numberOfBytesSentLastSecond = 0 # used for the bandwidth rate limit +lastTimeWeResetBytesReceived = 0 # used for the bandwidth rate limit +lastTimeWeResetBytesSent = 0 # used for the bandwidth rate limit +sendDataLock = threading.Lock() # used for the bandwidth rate limit +receiveDataLock = threading.Lock() # used for the bandwidth rate limit daemon = False inventorySets = {} # key = streamNumer, value = a set which holds the inventory object hashes that we are aware of. This is used whenever we receive an inv message from a peer to check to see what items are new to us. We don't delete things out of it; instead, the singleCleaner thread clears and refills it every couple hours. needToWriteKnownNodesToDisk = False # If True, the singleCleaner will write it to disk eventually. @@ -159,6 +165,15 @@ def assembleVersionMessage(remoteHost, remotePort, myStreamNumber): return CreatePacket('version', payload) +def assembleErrorMessage(fatal=0, banTime=0, inventoryVector='', errorText=''): + payload = encodeVarint(fatal) + payload += encodeVarint(banTime) + payload += encodeVarint(len(inventoryVector)) + payload += inventoryVector + payload += encodeVarint(len(errorText)) + payload += errorText + return CreatePacket('error', payload) + def lookupAppdataFolder(): APPNAME = "PyBitmessage" if "BITMESSAGE_HOME" in environ: @@ -586,11 +601,14 @@ Peer = collections.namedtuple('Peer', ['host', 'port']) def checkAndShareObjectWithPeers(data): """ - Function is called after either receiving an object off of the wire + This function is called after either receiving an object off of the wire or after receiving one as ackdata. Returns the length of time that we should reserve to process this message if we are receiving it off of the wire. """ + if len(data) > 2 ** 18: + logger.info('The payload length of this object is too large (%s bytes). Ignoring it.' % len(data)) + return # Let us check to make sure that the proof of work is sufficient. if not isProofOfWorkSufficient(data): logger.info('Proof of work is insufficient.')