244 lines
8.9 KiB
Text
244 lines
8.9 KiB
Text
Lazy Links
|
|
==========
|
|
|
|
(Ideas by Dianora and orabidoo; initial spec by orabidoo)
|
|
|
|
|
|
|
|
Basic idea: leaf servers don't really need to know everything about
|
|
every single user and channel out there; connecting a
|
|
new leaf server to the network should be fast, easy and
|
|
cheap, instead of taking ages to exchange information
|
|
about the state of the whole network.
|
|
|
|
The result is that we move, from a loop-less graph
|
|
topology, to a kind of starfish one, where hubs
|
|
form a core (interconnected by the traditional IRC
|
|
protocol), and leaves are just appendages on hubs.
|
|
|
|
|
|
In the rest of this text, we assume that the local network configuration
|
|
looks like this:
|
|
|
|
|
|
LLL <---> HUB <---> OH <---> ....
|
|
^
|
|
|
|
|
v
|
|
ALL
|
|
|
|
where LLL and ALL are Lazy Link Leaves, Hub is a Hub, and OH is anOther
|
|
hub.
|
|
|
|
|
|
1) Channels
|
|
|
|
Hubs, as usual, have full information about all channels, with their
|
|
membership, chanop status, TS, etc. This information is authoritative,
|
|
which means that they can use it to make decisions such as "should this
|
|
user be given ops at this point". This is just the way things are now
|
|
already. And, as usual, traditional leaves have all this information
|
|
too, and keep having it.
|
|
|
|
Lazy Leaves, OTOH, depend on their uplinks for much of their
|
|
information. They have partial information, meaning that they don't
|
|
have the full channel list. However, when they have something about a
|
|
channel, they do have *everything* about it, and their information is
|
|
authoritative, so they can decide locally on chanop matters.
|
|
|
|
For this, hubs need to know which channels each of its Lazy Leaves has.
|
|
This is necessarily a double-ended map; it can't be just a single flag
|
|
on each channel. For efficiency, it could be implemented on the hub by
|
|
adding a 32-bit int to the server-link structure, and assigning a
|
|
bitmask (one of 1, 2, 4, ... up to 0x80000000) to each of its Lazy Leaf
|
|
links. That would support up to 32 Lazy Leaves per hub, and make it
|
|
really easy and cheap to keep this information. (The only slight
|
|
downside being that, when a Lazy Leaf link breaks, you need to clear a
|
|
bit on every single channel.)
|
|
|
|
|
|
1.1) Joining
|
|
|
|
When a client on a LLL sends a "JOIN #channel", LLL does as usual: if it
|
|
has the channel locally, it just joins the user, sends an SJOIN to HUB,
|
|
and all is well; if it doesn't have the channel, it creates it, sends a
|
|
SJOIN to HUB with the current time as the TS. LLL tells the user that it
|
|
has joined the channel, but it doesn't tell it that it has ops yet. So
|
|
LLL sends
|
|
|
|
[LLL -> HUB] :LLL SJOIN LLL_TS #channel :@LLLuser
|
|
[LLL -> LLLuser] :LLLuser JOIN #channel
|
|
|
|
When HUB gets a SJOIN from LLL, it needs to do a lot of the deciding that
|
|
normally goes into m_join:
|
|
|
|
@) if LLL's bit is already set for #channel, then this is not the
|
|
first time LLL is dealing with #channel, so just process it as
|
|
a normal SJOIN.
|
|
|
|
otherwise:
|
|
|
|
a) if myuser cannot join by can_join rules, send a KICK to LLL:
|
|
[HUB --> LLL] :HUB KICK #channel LLLuser :sorry, the channel was +i
|
|
|
|
in this case, LLL's bit doesn't get set for #channel on HUB.
|
|
|
|
b) if myuser's join is OK and must be given ops (by usual TS
|
|
rules, meaning that either LLL_TS < HUB_TS, or the channel
|
|
is opless or didn't exist on the hub side), then HUB sends
|
|
something back that validates the join:
|
|
[HUB -> LLL] :HUB SJOIN OLDER_TS #channel +modes :@LLLuser +other users
|
|
|
|
c) if myuser's join is OK but must not be given ops, the HUB
|
|
sends the same kind of thing back, but without marking ops:
|
|
[HUB --> LLL] :HUB SJOIN OLDER_TS #channel +modes :LLLuser @other +users
|
|
|
|
in this case, as in case b), HUB sets LLL's bit for #channel,
|
|
so it knows that that LLL knows about that channel now.
|
|
|
|
When LLL gets a SJOIN from its hub that includes in the userlist one of
|
|
LLL's local users, it interprets that that validates a join. If LLLuser
|
|
has ops in that list, then LLL sends:
|
|
|
|
[LLL --> LLLuser] :HUB MODE #channel +o LLLuser
|
|
|
|
If not, it just skips that nick from the list. In either case, it
|
|
processes the rest of the SJOIN information (modes, other nicks) by the
|
|
usual SJOIN rules.
|
|
|
|
|
|
1.2) Bursts
|
|
|
|
The beauty of this is that, with the rules above, channel bursts get
|
|
avoided, without the need to do anything more.
|
|
|
|
When LLL and HUB connect to each other, LLL sends a channel burst as
|
|
usual; HUB doesn't. By the rules above, HUB will reply to each first
|
|
LLL's SJOIN for a channel with a SJOIN back with its own info. So at the
|
|
end of the burst, LLL has been put up to date with all the channels it
|
|
needs to know about.
|
|
|
|
|
|
1.3) Parts, Kicks and Modes
|
|
|
|
When one of LLL's clients (say, LLLuser) leaves a channel, or is kicked
|
|
out of it, LLL needs to check if that was the last of its clients for
|
|
that channel.
|
|
|
|
If that is the case, then LLL needs to inform HUB that it no longer holds
|
|
#channel, and destroy its local information about #channel:
|
|
|
|
[LLL -> HUB] :LLL DROP #channel
|
|
|
|
Upon receiving a "DROP" command from a Lazy Leaf, the Hub just clears
|
|
the Lazy Leaf's bit on that channel.
|
|
|
|
Alternatively, a Lazy Leaf could decide to cache channels even without
|
|
having any clients on them. All it has to do is not send the "DROP"
|
|
command to its hub.
|
|
|
|
For MODE commands coming from the rest of the net and related to
|
|
#channel, HUB only needs to pass them to LLL if LLL's bit is set for
|
|
#channel.
|
|
|
|
For MODE changes related to #channel and done by local users on LLL,
|
|
LLL just passes them as usual to HUB.
|
|
|
|
For the special "MODE #channel" query, done on LLL, for a channel that
|
|
doesn't exist on LLL, this must be routed through HUB:
|
|
|
|
[LLL --> HUB] :LLLuser MODE #channel
|
|
[HUB --> LLL] :HUB (numeric) #channel modes
|
|
|
|
|
|
2) Nicks
|
|
|
|
Nicks are simpler, because they are atomic, there is no list associated
|
|
with them.
|
|
|
|
Again, the hub needs to know, for each nick, which of its Lazy Leaves
|
|
know of it. This can be done with the same 32-bit bitmask as with
|
|
servers. For each user, the associated bit is 1, unless a NICK command
|
|
has been sent or received for that user, on the given Lazy Leaf link.
|
|
|
|
Once again too, the connect burst gets reduced to just the smaller side:
|
|
the Lazy Leaf dumps its user base on the hub, but not the other way
|
|
round.
|
|
|
|
When a Lazy Leaf gets a request from one of its local clients, that
|
|
relates to a nick LLL doesn't have, this must be routed through HUB.
|
|
|
|
|
|
2.1) WHOIS
|
|
|
|
For simplicity, we could kill multiple-destination WHOIS, if that's not
|
|
already done, and all kinds of WHOIS *pattern*.
|
|
|
|
When LLLuser does "WHOIS somenick", if the nick is known to LLL, it
|
|
replies normally. If it isn't, then LLL routes it to HUB:
|
|
|
|
[LLL --> HUB] :LLLuser WHOIS Somenick
|
|
|
|
HUB replies with the usual numeric, and also with a burst-style NICK
|
|
introduction, so that from that point on LLL knows about Somenick. HUB
|
|
also sets LLL's bit for Somenick.
|
|
|
|
[HUB --> LLL] :HUB (numerics) WHOIS info
|
|
[HUB --> LLL] NICK nickTS Somenick HopCount Umode ......
|
|
|
|
|
|
2.2) NOTIFY and USERHOST
|
|
|
|
These all take lists of users; for a NOTIFY or USERHOST on LLL from one
|
|
of its users, the server checks if *all* of the nicks involved are
|
|
known. If at least one isn't, then the request must be passed as such
|
|
to HUB. HUB then replies to the client, and also sends a NICK
|
|
introduction for each client that LLL didn't previously have.
|
|
|
|
Note: this kind of sucks, because most NOTIFY lines will tend to include
|
|
a nick or two that isn't on IRC at the moment, which means they will be
|
|
relayed. With almost every client out there having NOTIFY, this might
|
|
well nullify the whole advantage of nick laziness. Or maybe not.
|
|
Someone needs to do some math on it, or some testing, or both.
|
|
|
|
|
|
2.3) PRIVMSG and NOTICE
|
|
|
|
When LLLuser sends "PRIVMSG somenick :message", this must be sent to
|
|
HUB, even if somenick isn't known locally. HUB will figure it out,
|
|
and possibly send a numeric back.
|
|
|
|
Same for NOTICE.
|
|
|
|
|
|
2.4) Anything else???
|
|
|
|
We've missed a bunch here... NAMES, WHO, TRACE, and probably others I
|
|
can't think of.
|
|
|
|
NAMES actually belongs to channels, and might as well not get routed
|
|
(just don't reply) if a LLLUser tries a NAMES for a #channel that it
|
|
isn't on, and that LLL doesn't have any info on.
|
|
|
|
for WHO, if there's a pattern, just pass the entire command to HUB and
|
|
let it reply to LLLUser through the link (without introducing any extra
|
|
NICKs here).
|
|
|
|
for TRACE, if the nick is locally unknown, just pass the thing to HUB
|
|
and let it deal with it.
|
|
|
|
|
|
4) Avoiding Desyncs
|
|
|
|
There is one particularly treacherous potential desync: a Lazy Leaf is
|
|
convinced that it has authoritative information about a channel, but its
|
|
hub is convinced that the leaf doesn't. The hub doesn't keep sending
|
|
new information, so the leaf's info grows stale, but it keeps acting on
|
|
it, which eventually leads to wrong decisions.
|
|
|
|
It is important that the protocol ensures that such desyncs are
|
|
impossible. There should also be periodic cleanup, whereby a Lazy Leaf
|
|
scans its own channel-user list, and deletes its own information about
|
|
any channel on which it doesn't have any local users (and complains to
|
|
its opers about it, because that should never happen).
|
|
|