recv/send excepts + disconnect reasons

This commit is contained in:
Dpeta 2022-06-13 04:03:39 +02:00
parent 0e1f676acc
commit 5c4d21640d
2 changed files with 138 additions and 70 deletions

49
irc.py
View file

@ -3,6 +3,7 @@ import logging.config
import socket import socket
import random import random
import time import time
import json
from PyQt5 import QtCore, QtGui from PyQt5 import QtCore, QtGui
@ -45,9 +46,19 @@ class PesterIRC(QtCore.QThread):
self.NickServ = services.NickServ() self.NickServ = services.NickServ()
self.ChanServ = services.ChanServ() self.ChanServ = services.ChanServ()
def IRCConnect(self): def IRCConnect(self):
with open(_datadir + "server.json", "r") as server_file:
read_file = server_file.read()
server_file.close()
server_obj = json.loads(read_file)
server = self.config.server() server = self.config.server()
port = self.config.port() port = self.config.port()
self.cli = IRCClient(PesterHandler, host=server, port=int(port), nick=self.mainwindow.profile().handle, real_name='pcc31', blocking=True, timeout=120) self.cli = IRCClient(PesterHandler,
host=server,
port=int(port),
nick=self.mainwindow.profile().handle,
real_name='pcc31',
timeout=120,
ssl=server_obj['TLS'])
self.cli.command_handler.parent = self self.cli.command_handler.parent = self
self.cli.command_handler.mainwindow = self.mainwindow self.cli.command_handler.mainwindow = self.mainwindow
self.cli.connect() self.cli.connect()
@ -55,10 +66,10 @@ class PesterIRC(QtCore.QThread):
def run(self): def run(self):
try: try:
self.IRCConnect() self.IRCConnect()
except socket.error as se: except OSError as se:
self.stopIRC = se self.stopIRC = se
return return
while 1: while True:
res = True res = True
try: try:
PchumLog.debug("updateIRC()") PchumLog.debug("updateIRC()")
@ -66,13 +77,10 @@ class PesterIRC(QtCore.QThread):
except socket.timeout as se: except socket.timeout as se:
PchumLog.debug("timeout in thread %s" % (self)) PchumLog.debug("timeout in thread %s" % (self))
self.cli.close() self.cli.close()
self.stopIRC = se self.stopIRC = "%s, %s" % (type(se), se)
return return
except socket.error as se: except (OSError, IndexError) as se:
if self.registeredIRC: self.stopIRC = "%s, %s" % (type(se), se)
self.stopIRC = None
else:
self.stopIRC = se
PchumLog.debug("socket error, exiting thread") PchumLog.debug("socket error, exiting thread")
return return
else: else:
@ -98,6 +106,8 @@ class PesterIRC(QtCore.QThread):
raise se raise se
except socket.error as se: except socket.error as se:
raise se raise se
except (OSError, IndexError) as se:
raise se
except StopIteration: except StopIteration:
self.conn = self.cli.conn() self.conn = self.cli.conn()
return True return True
@ -366,10 +376,6 @@ class PesterIRC(QtCore.QThread):
def quit_dc(self): def quit_dc(self):
helpers.quit(self.cli, _pcVersion + " <3") helpers.quit(self.cli, _pcVersion + " <3")
#def getMask(self):
# This needs to be updated when our hostname is changed.
# Nevermind this entire thing, actually.
moodUpdated = QtCore.pyqtSignal('QString', Mood) moodUpdated = QtCore.pyqtSignal('QString', Mood)
colorUpdated = QtCore.pyqtSignal('QString', QtGui.QColor) colorUpdated = QtCore.pyqtSignal('QString', QtGui.QColor)
messageReceived = QtCore.pyqtSignal('QString', 'QString') messageReceived = QtCore.pyqtSignal('QString', 'QString')
@ -439,6 +445,14 @@ class PesterHandler(DefaultCommandHandler):
else: else:
# Invalid syntax # Invalid syntax
PchumLog.warning("TAGMSG with invalid syntax.") PchumLog.warning("TAGMSG with invalid syntax.")
def error(self, *params):
# Server is ending connection.
reason = ''
for x in params:
if (x != None) and (x != ''):
reason += x + ' '
self.parent.stopIRC = reason.strip()
self.parent.reconnectIRC()
def privmsg(self, nick, chan, msg): def privmsg(self, nick, chan, msg):
handle = nick[0:nick.find("!")] handle = nick[0:nick.find("!")]
@ -539,6 +553,15 @@ class PesterHandler(DefaultCommandHandler):
helpers.cap(self.client, "REQ", "message-tags") helpers.cap(self.client, "REQ", "message-tags")
helpers.cap(self.client, "REQ", "pesterchum-tag") helpers.cap(self.client, "REQ", "pesterchum-tag")
def erroneusnickname(self, *args):
# Server is not allowing us to connect.
reason = "Handle is not allowed on this server.\n"
for x in args:
if (x != None) and (x != ''):
reason += x + ' '
self.parent.stopIRC = reason.strip()
self.parent.reconnectIRC()
def keyvalue(self, target, handle_us, handle_owner, key, visibility, *value): def keyvalue(self, target, handle_us, handle_owner, key, visibility, *value):
# The format of the METADATA server notication is: # The format of the METADATA server notication is:
# METADATA <Target> <Key> <Visibility> <Value> # METADATA <Target> <Key> <Visibility> <Value>

View file

@ -27,7 +27,6 @@ import socket
import time import time
import traceback import traceback
import ssl import ssl
import json
import select import select
from oyoyo.parse import parse_raw_irc_command from oyoyo.parse import parse_raw_irc_command
@ -80,27 +79,7 @@ class IRCClient:
... ...
""" """
# This should be moved to profiles self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
with open(_datadir + "server.json", "r") as server_file:
read_file = server_file.read()
server_file.close()
server_obj = json.loads(read_file)
TLS = server_obj['TLS']
#print("TLS-status is: " + str(TLS))
if TLS == False:
#print("false")
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
else:
self.context = ssl.create_default_context()
self.context.check_hostname = False
self.context.verify_mode = ssl.CERT_NONE
self.bare_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket = self.context.wrap_socket(self.bare_socket)
#self.socket.setsockopt(socket.IPPROTO_TCP, socket.SO_KEEPALIVE, 1)
self.blocking = True
self.socket.setblocking(self.blocking)
self.nick = None self.nick = None
self.real_name = None self.real_name = None
@ -108,6 +87,8 @@ class IRCClient:
self.port = None self.port = None
self.connect_cb = None self.connect_cb = None
self.timeout = None self.timeout = None
self.blocking = None
self.ssl = None
self.__dict__.update(kwargs) self.__dict__.update(kwargs)
self.command_handler = cmd_handler(self) self.command_handler = cmd_handler(self)
@ -146,26 +127,44 @@ class IRCClient:
msg = bytes(" ", "UTF-8").join(bargs) msg = bytes(" ", "UTF-8").join(bargs)
PchumLog.info('---> send "%s"' % msg) PchumLog.info('---> send "%s"' % msg)
try: try:
# Extra block to give a failed write another try, causes a reconnect otherwise tries = 1
retry = 0 while tries < 10:
while retry < 5:
try: try:
ready_to_read, ready_to_write, in_error = select.select([], [self.socket], []) ready_to_read, ready_to_write, in_error = select.select([], [self.socket], [])
PchumLog.debug("ready_to_write (len %s): " % str(len(ready_to_write)) + str(ready_to_write))
#ready_to_write[0].sendall(msg + bytes("\r\n", "UTF-8"))
for x in ready_to_write: for x in ready_to_write:
x.sendall(msg + bytes("\r\n", "UTF-8")) x.sendall(msg + bytes("\r\n", "UTF-8"))
break break
except (socket.error, ValueError) as e:# "file descriptor cannot be a negative integer" except ssl.SSLWantReadError as e:
retry += 1 PchumLog.warning("ssl.SSLWantReadError on send, " + str(e))
PchumLog.warning("socket.error (retry %s) %s" % (str(retry), str(e))) select.select([self.socket], [], [])
except socket.error as se: if tries >= 9:
PchumLog.warning("socket.error %s" % str(se)) raise e
try: # a little dance of compatibility to get the errno except ssl.SSLWantWriteError as e:
errno = se.errno PchumLog.warning("ssl.SSLWantWriteError on send, " + str(e))
except AttributeError: select.select([], [self.socket], [])
errno = se[0] if tries >= 9:
if not self.blocking and errno == 11: raise e
except ssl.SSLEOFError as e:
# ssl.SSLEOFError guarantees a broken connection.
PchumLog.warning("ssl.SSLEOFError in on send, " + str(e))
raise ssl.SSLEOFError
except (socket.timeout, TimeoutError) as e:
# socket.timeout is deprecated in 3.10
PchumLog.warning("TimeoutError in on send, " + str(e))
raise socket.timeout
except (OSError, IndexError) as e:
# Unknown error, might as well retry?
PchumLog.warning("Unkown error on send, " + str(e))
if tries >= 9:
raise e
tries += 1
PchumLog.warning("Retrying send. (attempt %s)" % str(tries))
time.sleep(0.1)
PchumLog.debug("ready_to_write (len %s): " % str(len(ready_to_write)) + str(ready_to_write))
except OSError as se:
PchumLog.warning("socket.error %s" % str(se))
if not self.blocking and se.errno == 11:
pass pass
else: else:
raise se raise se
@ -173,16 +172,37 @@ class IRCClient:
def connect(self): def connect(self):
""" initiates the connection to the server set in self.host:self.port """ initiates the connection to the server set in self.host:self.port
""" """
PchumLog.info('connecting to %s:%s' % (self.host, self.port)) PchumLog.info('connecting to %s:%s' % (self.host, self.port))
self.socket.connect(("%s" % self.host, self.port))
if not self.blocking: if self.ssl == True:
self.socket.setblocking(0) context = ssl.create_default_context()
bare_sock = socket.create_connection(("%s" % self.host, self.port))
self.socket = context.wrap_socket(bare_sock, server_hostname=self.host, do_handshake_on_connect=False)
while True:
try:
self.socket.do_handshake()
break
except ssl.SSLWantReadError:
select.select([self.socket], [], [])
except ssl.SSLWantWriteError:
select.select([], [self.socket], [])
PchumLog.info("secure sockets version is %s" % self.socket.version())
else:
self.socket = socket.create_connection(("%s" % self.host, self.port))
# setblocking is a shorthand for timeout,
# we shouldn't use both.
if self.timeout: if self.timeout:
self.socket.settimeout(self.timeout) self.socket.settimeout(self.timeout)
elif not self.blocking:
self.socket.setblocking(False)
elif self.blocking:
self.socket.setblocking(True)
helpers.nick(self, self.nick) helpers.nick(self, self.nick)
helpers.user(self, self.nick, self.real_name) helpers.user(self, self.nick, self.real_name)
if self.connect_cb: if self.connect_cb:
self.connect_cb(self) self.connect_cb(self)
@ -191,24 +211,51 @@ class IRCClient:
try: try:
buffer = bytes() buffer = bytes()
while not self._end: while not self._end:
# Block for connection-killing exceptions
try: try:
ready_to_read, ready_to_write, in_error = select.select([self.socket], [], []) # Don't wanna cause an unnecessary timeout tries = 1
# Though this could probably be 90 while tries < 10:
PchumLog.debug("ready_to_read (len %s): " % len(ready_to_read) + str(ready_to_read)) try:
for x in ready_to_read: ready_to_read, ready_to_write, in_error = select.select([self.socket], [], [])
buffer += x.recv(1024) for x in ready_to_read:
#print("pre-recv") buffer += x.recv(1024)
#buffer += self.socket.recv(1024) break
#print("post-recv") except ssl.SSLWantReadError as e:
#print(buffer) PchumLog.warning("ssl.SSLWantReadError on send, " + str(e))
#raise socket.timeout select.select([self.socket], [], [])
if tries >= 9:
raise e
except ssl.SSLWantWriteError as e:
PchumLog.warning("ssl.SSLWantWriteError on send, " + str(e))
select.select([], [self.socket], [])
if tries >= 9:
raise e
except ssl.SSLEOFError as e:
# ssl.SSLEOFError guarantees a broken connection.
PchumLog.warning("ssl.SSLEOFError in on send, " + str(e))
raise ssl.SSLEOFError
except (socket.timeout, TimeoutError) as e:
# socket.timeout is deprecated in 3.10
PchumLog.warning("TimeoutError in on send, " + str(e))
raise socket.timeout
except (OSError, IndexError) as e:
# Unknown error, might as well retry?
PchumLog.warning("Unkown error, " + str(e))
if tries >= 9:
raise e
tries += 1
PchumLog.warning("Retrying recv. (attempt %s)" % str(tries))
time.sleep(0.1)
except socket.timeout as e: except socket.timeout as e:
PchumLog.warning("timeout in client.py, " + str(e)) PchumLog.warning("timeout in client.py, " + str(e))
if self._end: if self._end:
break break
raise e raise e
except (socket.error, ValueError) as e: except ssl.SSLEOFError:
PchumLog.warning("conn socket.error %s in %s" % (e, self)) raise ssl.SSLEOFError
except OSError as e:
PchumLog.warning("conn exception %s in %s" % (e, self))
if self._end: if self._end:
break break
try: # a little dance of compatibility to get the errno try: # a little dance of compatibility to get the errno
@ -222,7 +269,6 @@ class IRCClient:
else: else:
if self._end: if self._end:
break break
PchumLog.debug("pre buffer check, len(buffer) = " + str(len(buffer)))
if len(buffer) == 0 and self.blocking: if len(buffer) == 0 and self.blocking:
PchumLog.debug("len(buffer) = 0") PchumLog.debug("len(buffer) = 0")
raise socket.error("Connection closed") raise socket.error("Connection closed")
@ -233,7 +279,6 @@ class IRCClient:
PchumLog.debug("data = " + str(data)) PchumLog.debug("data = " + str(data))
for el in data: for el in data:
PchumLog.debug("el=%s, data=%s" % (el,data))
tags, prefix, command, args = parse_raw_irc_command(el) tags, prefix, command, args = parse_raw_irc_command(el)
try: try:
# Only need tags with tagmsg # Only need tags with tagmsg
@ -248,7 +293,7 @@ class IRCClient:
except socket.timeout as se: except socket.timeout as se:
PchumLog.debug("passing timeout") PchumLog.debug("passing timeout")
raise se raise se
except socket.error as se: except (socket.error, ssl.SSLEOFError) as se:
PchumLog.debug("problem: %s" % (str(se))) PchumLog.debug("problem: %s" % (str(se)))
if self.socket: if self.socket:
PchumLog.info('error: closing socket') PchumLog.info('error: closing socket')