|
|
|
|
@ -700,8 +700,8 @@ class OnionMessageChannel(MessageChannel):
|
|
|
|
|
self.wait_for_directories_loop = None |
|
|
|
|
|
|
|
|
|
# this dict plays the same role as `active_channels` in `MessageChannelCollection`. |
|
|
|
|
# it has structure {nick: set(),..} where set() has elements that are dicts: |
|
|
|
|
# {OnionPeer: bool}. |
|
|
|
|
# it has structure {nick1: {}, nick2: {}, ...} where the inner dicts are: |
|
|
|
|
# {OnionDirectoryPeer1: bool, OnionDirectoryPeer2: bool, ...}. |
|
|
|
|
# Entries get updated with changing connection status of directories, |
|
|
|
|
# allowing us to decide where to send each message we want to send when we have no |
|
|
|
|
# direct connection. |
|
|
|
|
@ -1057,6 +1057,33 @@ class OnionMessageChannel(MessageChannel):
|
|
|
|
|
return |
|
|
|
|
self.send_peers(peer, peer_filter=[peer_from]) |
|
|
|
|
|
|
|
|
|
def on_nick_leave_directory(self, nick: str, dir_peer: OnionPeer) -> None: |
|
|
|
|
""" This is called in response to a disconnection control |
|
|
|
|
message from a directory, telling us that a certain nick has left. |
|
|
|
|
We update this connection status in the active_directories map, |
|
|
|
|
and fire the MessageChannel.on_nick_leave when we see all the |
|
|
|
|
connections are lost. |
|
|
|
|
Note that `on_nick_leave` can be triggered in two ways; both here, |
|
|
|
|
and also via `self.register_disconnection`, which occurs for peers |
|
|
|
|
to whom we are directly connected. Calling it multiple times is not |
|
|
|
|
harmful, but remember that the on_nick_leave event only bubbles up |
|
|
|
|
above the message channel layer once *all* message channels trigger |
|
|
|
|
on_nick_leave (in case we are using another message channel as well |
|
|
|
|
as this one, like IRC). |
|
|
|
|
""" |
|
|
|
|
if not nick in self.active_directories: |
|
|
|
|
return |
|
|
|
|
if not dir_peer in self.active_directories[nick]: |
|
|
|
|
log.info("Directory {} is telling us that {} has left, but we " |
|
|
|
|
"didn't know about them. Ignoring.".format( |
|
|
|
|
dir_peer.peer_location(), nick)) |
|
|
|
|
return |
|
|
|
|
log.debug("Directory {} has lost connection to: {}".format( |
|
|
|
|
dir_peer.peer_location(), nick)) |
|
|
|
|
self.active_directories[nick][dir_peer] = False |
|
|
|
|
if not any(self.active_directories[nick].values()): |
|
|
|
|
self.on_nick_leave(nick, self) |
|
|
|
|
|
|
|
|
|
def process_control_message(self, peerid: str, msgtype: int, |
|
|
|
|
msgval: str) -> bool: |
|
|
|
|
""" Triggered by a directory node feeding us |
|
|
|
|
@ -1084,10 +1111,24 @@ class OnionMessageChannel(MessageChannel):
|
|
|
|
|
return True |
|
|
|
|
try: |
|
|
|
|
peerlist = msgval.split(",") |
|
|
|
|
for peer in peerlist: |
|
|
|
|
for peer_in_list in peerlist: |
|
|
|
|
# directories should send us peerstrings that include |
|
|
|
|
# nick;host:port;D where "D" indicates that the directory |
|
|
|
|
# is signalling this peer as having left. Otherwise, without |
|
|
|
|
# the third field, we treat it as a "join" event. |
|
|
|
|
try: |
|
|
|
|
nick, hostport, disconnect_code = peer_in_list.split( |
|
|
|
|
NICK_PEERLOCATOR_SEPARATOR) |
|
|
|
|
if disconnect_code != "D": |
|
|
|
|
continue |
|
|
|
|
self.on_nick_leave_directory(nick, peer) |
|
|
|
|
continue |
|
|
|
|
except ValueError: |
|
|
|
|
# just means this message is not of the 'disconnect' type |
|
|
|
|
pass |
|
|
|
|
# defaults mean we just add the peer, not |
|
|
|
|
# add or alter its connection status: |
|
|
|
|
self.add_peer(peer, with_nick=True) |
|
|
|
|
self.add_peer(peer_in_list, with_nick=True) |
|
|
|
|
except Exception as e: |
|
|
|
|
log.debug("Incorrectly formatted peer list: {}, " |
|
|
|
|
"ignoring, {}".format(msgval, e)) |
|
|
|
|
@ -1118,6 +1159,13 @@ class OnionMessageChannel(MessageChannel):
|
|
|
|
|
msgval = self.get_peer_by_id(msgval).peer_location() |
|
|
|
|
self.add_peer(msgval, connection=False, |
|
|
|
|
overwrite_connection=True) |
|
|
|
|
if self.self_as_peer.directory: |
|
|
|
|
# We propagate the control message as a "peerlist" with |
|
|
|
|
# the "D" flag: |
|
|
|
|
disconnected_peer = self.get_peer_by_id(msgval) |
|
|
|
|
for p in self.get_connected_nondirectory_peers(): |
|
|
|
|
self.send_peers(p, peer_filter=[disconnected_peer], |
|
|
|
|
disconnect=True) |
|
|
|
|
# bubble up the disconnection event to the abstract |
|
|
|
|
# message channel logic: |
|
|
|
|
if self.on_nick_leave: |
|
|
|
|
@ -1310,8 +1358,12 @@ class OnionMessageChannel(MessageChannel):
|
|
|
|
|
try: |
|
|
|
|
nick, peer = peerdata.split(NICK_PEERLOCATOR_SEPARATOR) |
|
|
|
|
except Exception as e: |
|
|
|
|
# TODO: as of now, this is not an error, but expected. |
|
|
|
|
# Don't log? Do something else? |
|
|
|
|
# old code does not recognize messages with "D" as a third |
|
|
|
|
# field; they will swallow the message here, ignoring |
|
|
|
|
# the message as invalid because it has three fields |
|
|
|
|
# instead of two. |
|
|
|
|
# (We still use the catch-all `Exception`, for the usual reason |
|
|
|
|
# of not wanting to make assumptions about external input). |
|
|
|
|
log.debug("Received invalid peer identifier string: {}, {}".format( |
|
|
|
|
peerdata, e)) |
|
|
|
|
return |
|
|
|
|
@ -1361,7 +1413,7 @@ class OnionMessageChannel(MessageChannel):
|
|
|
|
|
return [p for p in self.peers if p.directory and p.status() == \ |
|
|
|
|
PEER_STATUS_HANDSHAKED] |
|
|
|
|
|
|
|
|
|
def get_connected_nondirectory_peers(self) -> list: |
|
|
|
|
def get_connected_nondirectory_peers(self) -> List[OnionPeer]: |
|
|
|
|
return [p for p in self.peers if (not p.directory) and p.status() == \ |
|
|
|
|
PEER_STATUS_HANDSHAKED] |
|
|
|
|
|
|
|
|
|
@ -1397,41 +1449,51 @@ class OnionMessageChannel(MessageChannel):
|
|
|
|
|
""" CONTROL MESSAGES SENT BY US |
|
|
|
|
""" |
|
|
|
|
def send_peers(self, requesting_peer: OnionPeer, |
|
|
|
|
peer_filter: List[OnionPeer]) -> None: |
|
|
|
|
peer_filter: List[OnionPeer], disconnect: bool=False) -> None: |
|
|
|
|
""" This message is sent by directory peers, currently |
|
|
|
|
only when a privmsg has to be forwarded to them. It |
|
|
|
|
could also be sent by directories to non-directory peers |
|
|
|
|
according to some other algorithm. |
|
|
|
|
If peer_filter is specified, only those peers will be sent. |
|
|
|
|
only when a privmsg has to be forwarded to them, or a peer has |
|
|
|
|
disconnected. It could also be sent by directories to non-directory |
|
|
|
|
peers according to some other algorithm. |
|
|
|
|
The message is sent *to* `requesting_peer`. |
|
|
|
|
If `peer_filter` is specified, only those peers will be sent. |
|
|
|
|
If `disconnect` is True, we append "D" to every entry, which |
|
|
|
|
indicates to the receiver that the peer being sent has left, |
|
|
|
|
not that that peer is available. |
|
|
|
|
The peerlist message should have this format: |
|
|
|
|
(1) entries comma separated |
|
|
|
|
(2) each entry is serialized nick then the NICK_PEERLOCATOR_SEPARATOR |
|
|
|
|
then host:port |
|
|
|
|
(3) Peers that do not have a reachable location are not sent. |
|
|
|
|
(2) each entry a two- or three- element list, separated by NICK_PEERLOCATOR_SEPARATOR, |
|
|
|
|
[nick, host:port] or same with ["D"] added at the end. |
|
|
|
|
For the case disconnect=False, peers that do not have a reachable location are not sent. |
|
|
|
|
""" |
|
|
|
|
if not requesting_peer.status() == PEER_STATUS_HANDSHAKED: |
|
|
|
|
raise OnionPeerConnectionError( |
|
|
|
|
"Cannot send peer list to unhandshaked peer") |
|
|
|
|
peerlist = set() |
|
|
|
|
peer_filter_exists = len(peer_filter) > 0 |
|
|
|
|
for p in self.get_connected_nondirectory_peers(): |
|
|
|
|
# don't send a peer to itself |
|
|
|
|
if p == requesting_peer: |
|
|
|
|
continue |
|
|
|
|
if peer_filter_exists and p not in peer_filter: |
|
|
|
|
continue |
|
|
|
|
if p.status() != PEER_STATUS_HANDSHAKED: |
|
|
|
|
# don't advertise what is not online. |
|
|
|
|
continue |
|
|
|
|
# peers that haven't sent their nick yet are not |
|
|
|
|
# privmsg-reachable; don't send them |
|
|
|
|
if p.nick == "": |
|
|
|
|
continue |
|
|
|
|
if p.peer_location() == NOT_SERVING_ONION_HOSTNAME: |
|
|
|
|
# if a connection has no reachable destination, |
|
|
|
|
# don't forward it |
|
|
|
|
continue |
|
|
|
|
peerlist.add(p.get_nick_peerlocation_ser()) |
|
|
|
|
if disconnect is False: |
|
|
|
|
for p in self.get_connected_nondirectory_peers(): |
|
|
|
|
# don't send a peer to itself |
|
|
|
|
if p == requesting_peer: |
|
|
|
|
continue |
|
|
|
|
if peer_filter_exists and p not in peer_filter: |
|
|
|
|
continue |
|
|
|
|
if p.status() != PEER_STATUS_HANDSHAKED: |
|
|
|
|
# don't advertise what is not online. |
|
|
|
|
continue |
|
|
|
|
# peers that haven't sent their nick yet are not |
|
|
|
|
# privmsg-reachable; don't send them |
|
|
|
|
if p.nick == "": |
|
|
|
|
continue |
|
|
|
|
if p.peer_location() == NOT_SERVING_ONION_HOSTNAME: |
|
|
|
|
# if a connection has no reachable destination, |
|
|
|
|
# don't forward it |
|
|
|
|
continue |
|
|
|
|
peerlist.add(p.get_nick_peerlocation_ser()) |
|
|
|
|
else: |
|
|
|
|
# since the peer may already be removed from self.peers, |
|
|
|
|
# we don't limit except by filter: |
|
|
|
|
for p in peer_filter: |
|
|
|
|
peerlist.add(p.get_nick_peerlocation_ser() + NICK_PEERLOCATOR_SEPARATOR + "D") |
|
|
|
|
# For testing: dns won't usually participate: |
|
|
|
|
peerlist.add(self.self_as_peer.get_nick_peerlocation_ser()) |
|
|
|
|
# don't send an empty set (will not be possible unless |
|
|
|
|
|