Archive for the ‘IRC’ Category

Logotron: active_disconnect.vpatch

Saturday, September 14th, 2019

Update 03.09.2019: The vpatches reground to sept_fixes are at:

curl 'http://bvt-trace.net/vpatches/active_disconnect_r3.kv.vpatch' > active_disconnect_r3.kv.vpatch
curl 'http://bvt-trace.net/vpatches/active_disconnect_r3.kv.vpatch.bvt.sig' > active_disconnect_r3.kv.vpatch.bvt.sig

This patch modifies the behavior of logotron IRC bot to close the socket on error and initialize it anew when connecting to a new server. It also has some additional changes:

  • I disabled Nagle's algorithm on a socket, as it has no business being enabled on low-bandwidth low-rate IRC links.
  • I replaced 'Listen socket' with 'Receive socket' in the error messages: 'listen' is a term of art for sockets accepting connections, so it looked confusing to me - there is no listening in BSD sockets sense of this word anywhere in the bot.
  • I also removed SO_KEEPALIVE socket option which got into hastily prepared initial version of vpatch. TCP keepalive messages are first sent after two hours of idleness on the socket - long after ~250 seconds of IRC idleness timeout expire.
def init_socket():
    global sock
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # Disable Nagle's algorithm for transmit operations
    sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
    # Disable Nagle's algorithm for receive operation, Linux-only
    try:
        sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_QUICKACK, 1)
    except Exception as e:
        logging.warning(e)

    connected = False

def deinit_socket():
    global connected
    global sock
    sock.close()
    connected = False

@@ -171,7 +188,7 @@
         sock.send(message.encode("utf-8"))
     except (socket.timeout, socket.error) as e:
         logging.warning("Socket could not send! Disconnecting.")
-        connected = False
+        deinit_socket()
         return False
     except Exception as e:
         logging.exception(e)
@@ -219,6 +236,10 @@
 def irc():
     global connected
     global time_last_conn
+    global sock
+
+    # Initialize a socket
+    init_socket()

     # Connect to one among the specified servers, in given priority :
     while not connected:
@@ -246,20 +267,20 @@
         try:
             data = sock.recv(Buf_Size)
         except socket.timeout as e:
-            logging.debug("Listen timed out")
+            logging.debug("Receive timed out")
             continue
         except socket.error as e:
-            logging.warning("Listen socket error, disconnecting.")
-            connected = False
+            logging.warning("Receive socket error, disconnecting.")
+            deinit_socket()
             continue
         except Exception as e:
             logging.exception(e)
-            connected = False
+            deinit_socket()
             continue
         else:
             if len(data) == 0:
-                logging.warning("Listen socket closed, disconnecting.")
-                connected = False
+                logging.warning("Receive socket closed, disconnecting.")
+                deinit_socket()
                 continue
             try:
                 try:

It was tested only a little using strace's error injection:

$ strace -f -einject=sendto,recvfrom,connect:error=ECONNRESET:when=10+7 -p PID_OF_bot.py

Patches available here:

curl 'http://bvt-trace.net/vpatches/active_disconnect.kv.vpatch' > active_disconnect.kv.vpatch
curl 'http://bvt-trace.net/vpatches/active_disconnect.kv.vpatch.bvt.sig' > active_disconnect.kv.vpatch.bvt.sig