From c49147f232683a1d0640aeb308f44fae129a0531 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sun, 4 Mar 2018 12:11:50 -0800 Subject: [PATCH 1/9] stats: route permission error replies to notice and not privmsg This prevents "unknown command" flood loops with stats services which poll these on link. --- plugins/stats.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/stats.py b/plugins/stats.py index 7e7710f..2aff7e6 100644 --- a/plugins/stats.py +++ b/plugins/stats.py @@ -87,7 +87,8 @@ def handle_stats(irc, source, command, args): try: permissions.check_permissions(irc, source, perms) except utils.NotAuthorizedError as e: - irc.msg(source, 'Error: %s' % e) # Note, no irc.error() because this is not a command, but a handler + # Note, no irc.error() because this is not a command, but a handler + irc.msg(source, 'Error: %s' % e, notice=True) return log.info('(%s) /STATS %s requested by %s', irc.name, stats_type, irc.get_hostmask(source)) From 8f9b56e9d9b2672daf4a8b0ecc3662c0f5956e2d Mon Sep 17 00:00:00 2001 From: James Lu Date: Wed, 7 Mar 2018 18:30:14 -0800 Subject: [PATCH 2/9] IRCNetwork: abort when _send() fails to avoid deadlocks --- classes.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/classes.py b/classes.py index 6e17281..9828f6e 100644 --- a/classes.py +++ b/classes.py @@ -1594,8 +1594,9 @@ class IRCNetwork(PyLinkNetworkCoreWithUtils): try: self._socket.send(encoded_data) - except (OSError, AttributeError): - log.exception("(%s) Failed to send message %r; did the network disconnect?", self.name, data) + except: + log.exception("(%s) Failed to send message %r; aborting!", self.name, data) + self.disconnect() def send(self, data, queue=True): """send() wrapper with optional queueing support.""" From ccc9f8e5c8f0f51f513e1d56b2b8eacf9e5936d9 Mon Sep 17 00:00:00 2001 From: James Lu Date: Wed, 7 Mar 2018 18:31:43 -0800 Subject: [PATCH 3/9] IRCNetwork: also catch ssl.SSLWantReadError and ssl.SSLWantWriteError --- classes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classes.py b/classes.py index 9828f6e..2dbe63c 100644 --- a/classes.py +++ b/classes.py @@ -1554,7 +1554,7 @@ class IRCNetwork(PyLinkNetworkCoreWithUtils): try: data = self._socket.recv(2048) - except BlockingIOError: + except (BlockingIOError, ssl.SSLWantReadError, ssl.SSLWantWriteError): log.debug('(%s) No data to read, trying again later...', self.name) if self._aborted.wait(self.SOCKET_REPOLL_WAIT): break From 92460716d1bf432d2255fe35701ef54f536442cc Mon Sep 17 00:00:00 2001 From: James Lu Date: Wed, 7 Mar 2018 18:32:20 -0800 Subject: [PATCH 4/9] IRCNetwork: bump SOCKET_REPOLL_WAIT to 1 sec --- classes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classes.py b/classes.py index 2dbe63c..c7ed5e0 100644 --- a/classes.py +++ b/classes.py @@ -1295,7 +1295,7 @@ utils._proto_utils_class = PyLinkNetworkCoreWithUtils # Used by compatibility w class IRCNetwork(PyLinkNetworkCoreWithUtils): S2S_BUFSIZE = 510 - SOCKET_REPOLL_WAIT = 0.5 + SOCKET_REPOLL_WAIT = 1.0 def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) From b6bac994c6d9b193924d62c6a47b253957b5f34a Mon Sep 17 00:00:00 2001 From: James Lu Date: Wed, 7 Mar 2018 22:28:34 -0800 Subject: [PATCH 5/9] servermaps: show the uplink server name for Clientbot links --- plugins/servermaps.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/servermaps.py b/plugins/servermaps.py index c562fb6..306fbd3 100644 --- a/plugins/servermaps.py +++ b/plugins/servermaps.py @@ -98,6 +98,10 @@ def _map(irc, source, args, show_relay=True): if remoteirc.has_cap('can-track-servers'): # Only ever show relay subservers once - this prevents infinite loops. showall(remoteirc, remoteirc.sid, hops=hops, is_relay_server=True) + else: + # For Clientbot links, show the server we're actually connected to. + reply("%s\x02%s\x02 (actual server name)" % + (' '*(hops+1), remoteirc.uplink)) else: # Afterwards, decrement the hopcount. From 57334183806d2295e251531f235d6e170d539879 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sat, 10 Mar 2018 19:54:33 -0800 Subject: [PATCH 6/9] relay_cb: bandaid fix to prevent STATUSMSG messages from being interpreted as non-channel specific --- plugins/relay_clientbot.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/plugins/relay_clientbot.py b/plugins/relay_clientbot.py index e6b7088..a102677 100644 --- a/plugins/relay_clientbot.py +++ b/plugins/relay_clientbot.py @@ -60,7 +60,7 @@ def cb_relay_core(irc, source, command, args): real_command = 'ACTION' - elif not irc.is_channel(args['target']): + elif not irc.is_channel(args['target'].lstrip(''.join(irc.prefixmodes.values()))): # Target is a user; handle this accordingly. if relay_conf.get('allow_clientbot_pms'): real_command = 'PNOTICE' if args.get('is_notice') else 'PM' @@ -113,8 +113,12 @@ def cb_relay_core(irc, source, command, args): netname = sourcenet # Figure out where the message is destined to. - target = args.get('channel') or args.get('target') - if target is None or not (irc.is_channel(target) or private): + stripped_target = target = args.get('channel') or args.get('target') + if target is not None: + # HACK: cheap fix to prevent @#channel messages from interpreted as non-channel specific + stripped_target = target.lstrip(''.join(irc.prefixmodes.values())) + + if target is None or not (irc.is_channel(stripped_target) or private): # Non-channel specific message (e.g. QUIT or NICK). If this isn't a PM, figure out # all channels that the sender shares over the relay, and relay them to those # channels. @@ -127,7 +131,7 @@ def cb_relay_core(irc, source, command, args): else: # Pluralize the channel so that we can iterate over it. targets = [target] - args['channel'] = target + args['channel'] = stripped_target log.debug('(%s) relay_cb_core: Relaying event %s to channels: %s', irc.name, real_command, targets) identhost = '' From b7b49769e0794ca6b654268b7d25fd4105594c34 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sun, 11 Mar 2018 21:25:42 -0700 Subject: [PATCH 7/9] relay: silently abort if a network splits while we try to spawn a server or client --- plugins/relay.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/plugins/relay.py b/plugins/relay.py index 9affe13..1dc2ba3 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -281,14 +281,9 @@ def get_relay_server_sid(irc, remoteirc, spawn_if_missing=True): sid = spawn_relay_server(irc, remoteirc) log.debug('(%s) get_relay_server_sid: got %s for %s.relay', irc.name, sid, remoteirc.name) - if sid not in irc.servers: - log.warning('(%s) Possible desync? SID %s for %s.relay doesn\'t exist anymore', irc.name, sid, remoteirc.name) - # Our stored server doesn't exist anymore. This state is probably a holdover from a netsplit, - # so let's refresh it. - sid = spawn_relay_server(irc, remoteirc) - elif sid in irc.servers and irc.servers[sid].remote != remoteirc.name: - log.debug('(%s) Possible desync? SID %s for %s.relay doesn\'t exist anymore is mismatched (got %s.relay)', irc.name, irc.servers[sid].remote, remoteirc.name) - sid = spawn_relay_server(irc, remoteirc) + if (sid not in irc.servers) or (sid in irc.servers and irc.servers[sid].remote != remoteirc.name): + # SID changed in the meantime; abort. + return log.debug('(%s) get_relay_server_sid: got %s for %s.relay (round 2)', irc.name, sid, remoteirc.name) spawnlocks_servers[irc.name].release() @@ -336,7 +331,7 @@ def spawn_relay_user(irc, remoteirc, user, times_tagged=0): rsid = get_relay_server_sid(remoteirc, irc) if not rsid: - log.error('(%s) spawn_relay_user: aborting user spawn for %s/%s @ %s (failed to retrieve a ' + log.debug('(%s) spawn_relay_user: aborting user spawn for %s/%s @ %s (failed to retrieve a ' 'working SID).', irc.name, user, nick, remoteirc.name) return try: From 5bffe6741677a2dd0e89443488ccf9c1bdb117d5 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sun, 11 Mar 2018 21:26:37 -0700 Subject: [PATCH 8/9] relay: bandaid patch for freezes on startup when there are a ton of networks The side effect of this patch is that it makes large bursts *really* CPU intensive. A proper fix for this will hopefully be introduced in the future. --- plugins/relay.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/plugins/relay.py b/plugins/relay.py index 1dc2ba3..b856776 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -33,10 +33,7 @@ default_permissions = {"*!*@*": ['relay.linked'], def initialize_all(irc): """Initializes all relay channels for the given IRC object.""" - # Wait for all IRC objects to be created first. This prevents - # relay servers from being spawned too early (before server authentication), - # which would break connections. - if world.started.wait(TCONDITION_TIMEOUT): + def _initialize_all(): for chanpair, entrydata in db.items(): network, channel = chanpair @@ -48,6 +45,14 @@ def initialize_all(irc): if network == irc.name: initialize_channel(irc, channel) + # Wait for all IRC objects to be created first. This prevents + # relay servers from being spawned too early (before server authentication), + # which would break connections. + if world.started.wait(TCONDITION_TIMEOUT): + t = threading.Thread(target=_initialize_all, daemon=True, + name='relay initialize_all thread from network %r' % irc.name) + t.start() + def main(irc=None): """Main function, called during plugin loading at start.""" log.debug('relay.main: loading links database') @@ -500,9 +505,8 @@ def initialize_channel(irc, channel): # from the config. Skip this. continue - # Give each network a tiny bit of leeway to finish up its connection. - # This is better than just dropping users their completely. - if not remoteirc.connected.wait(TCONDITION_TIMEOUT): + # Remote net isn't ready yet, try again later. + if not remoteirc.connected.is_set(): continue # Join their (remote) users and set their modes, if applicable. From 180bfa991761c3606b7f6c7f60847eb8e922f28c Mon Sep 17 00:00:00 2001 From: James Lu Date: Sat, 17 Mar 2018 10:27:56 -0700 Subject: [PATCH 9/9] relay: don't spam ulines with "notice failed" errors --- plugins/relay.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/plugins/relay.py b/plugins/relay.py index b856776..b6f27db 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -1474,16 +1474,20 @@ def handle_messages(irc, numeric, command, args): # Otherwise, the sender doesn't have a client representing them # on the remote network, and we won't have anything to send our # messages from. + # Note: don't spam ulined senders (e.g. services announcers) with + # these notices. if homenet not in remoteusers.keys(): - irc.msg(numeric, 'You must be in a common channel ' - 'with %r in order to send messages.' % \ - irc.users[target].nick, notice=True) + if not _is_uline(irc, numeric): + irc.msg(numeric, 'You must be in a common channel ' + 'with %r in order to send messages.' % \ + irc.users[target].nick, notice=True) return remoteirc = world.networkobjects[homenet] if (not remoteirc.has_cap('can-spawn-clients')) and not conf.conf.get('relay', {}).get('allow_clientbot_pms'): - irc.msg(numeric, 'Private messages to users connected via Clientbot have ' - 'been administratively disabled.', notice=True) + if not _is_uline(irc, numeric): + irc.msg(numeric, 'Private messages to users connected via Clientbot have ' + 'been administratively disabled.', notice=True) return user = get_remote_user(irc, remoteirc, numeric, spawn_if_missing=False)