| 1 |
$Id$ |
| 2 |
Lazy Links |
| 3 |
========== |
| 4 |
(Ideas by Dianora and orabidoo; initial spec by orabidoo) |
| 5 |
|
| 6 |
Basic idea: leaf servers don't really need to know everything about |
| 7 |
every single user and channel out there; connecting a |
| 8 |
new leaf server to the network should be fast, easy and |
| 9 |
cheap, instead of taking ages to exchange information |
| 10 |
about the state of the whole network. |
| 11 |
|
| 12 |
The result is that we move, from a loop-less graph |
| 13 |
topology, to a kind of starfish one, where hubs |
| 14 |
form a core (interconnected by the traditional IRC |
| 15 |
protocol), and leaves are just appendages on hubs. |
| 16 |
|
| 17 |
|
| 18 |
In the rest of this text, we assume that the local network configuration |
| 19 |
looks like this: |
| 20 |
|
| 21 |
|
| 22 |
LLL <---> HUB <---> OH <---> .... |
| 23 |
^ |
| 24 |
| |
| 25 |
v |
| 26 |
ALL |
| 27 |
|
| 28 |
where LLL and ALL are Lazy Link Leaves, Hub is a Hub, and OH is another |
| 29 |
hub. |
| 30 |
|
| 31 |
|
| 32 |
1) Channels |
| 33 |
|
| 34 |
Hubs, as usual, have full information about all channels, with their |
| 35 |
membership, chanop status, TS, etc. This information is authoritative, |
| 36 |
which means that they can use it to make decisions such as "should this |
| 37 |
user be given ops at this point". This is just the way things are now |
| 38 |
already. And, as usual, traditional leaves have all this information |
| 39 |
too, and keep having it. |
| 40 |
|
| 41 |
Lazy Leaves, OTOH, depend on their uplinks for much of their |
| 42 |
information. They have partial information, meaning that they don't |
| 43 |
have the full channel list. However, when they have something about a |
| 44 |
channel, they do have *everything* about it, and their information is |
| 45 |
authoritative, so they can decide locally on chanop matters. |
| 46 |
|
| 47 |
For this, hubs need to know which channels each of its Lazy Leaves has. |
| 48 |
This is necessarily a double-ended map; it can't be just a single flag |
| 49 |
on each channel. For efficiency, it could be implemented on the hub by |
| 50 |
adding a 32-bit int to the server-link structure, and assigning a |
| 51 |
bitmask (one of 1, 2, 4, ... up to 0x80000000) to each of its Lazy Leaf |
| 52 |
links. That would support up to 32 Lazy Leaves per hub, and make it |
| 53 |
really easy and cheap to keep this information. (The only slight |
| 54 |
downside being that, when a Lazy Leaf link breaks, you need to clear a |
| 55 |
bit on every single channel.) |
| 56 |
|
| 57 |
|
| 58 |
1.1) Joining |
| 59 |
|
| 60 |
When a client on a LLL sends a "JOIN #channel", LLL does as usual: if it |
| 61 |
has the channel locally, it just joins the user, sends an SJOIN to HUB, |
| 62 |
and all is well; if it doesn't have the channel, it creates it, sends a |
| 63 |
SJOIN to HUB with the current time as the TS. LLL tells the user that it |
| 64 |
has joined the channel, but it doesn't tell it that it has ops yet. So |
| 65 |
LLL sends |
| 66 |
|
| 67 |
[LLL -> HUB] :LLL SJOIN LLL_TS #channel :@LLLuser |
| 68 |
[LLL -> LLLuser] :LLLuser JOIN #channel |
| 69 |
|
| 70 |
When HUB gets a SJOIN from LLL, it needs to do a lot of the deciding that |
| 71 |
normally goes into m_join: |
| 72 |
|
| 73 |
@) if LLL's bit is already set for #channel, then this is not the |
| 74 |
first time LLL is dealing with #channel, so just process it as |
| 75 |
a normal SJOIN. |
| 76 |
|
| 77 |
otherwise: |
| 78 |
|
| 79 |
a) if myuser cannot join by can_join rules, send a KICK to LLL: |
| 80 |
[HUB --> LLL] :HUB KICK #channel LLLuser :sorry, the channel was +i |
| 81 |
|
| 82 |
in this case, LLL's bit doesn't get set for #channel on HUB. |
| 83 |
|
| 84 |
b) if myuser's join is OK and must be given ops (by usual TS |
| 85 |
rules, meaning that either LLL_TS < HUB_TS, or the channel |
| 86 |
is opless or didn't exist on the hub side), then HUB sends |
| 87 |
something back that validates the join: |
| 88 |
[HUB -> LLL] :HUB SJOIN OLDER_TS #channel +modes :@LLLuser +other users |
| 89 |
|
| 90 |
c) if myuser's join is OK but must not be given ops, the HUB |
| 91 |
sends the same kind of thing back, but without marking ops: |
| 92 |
[HUB --> LLL] :HUB SJOIN OLDER_TS #channel +modes :LLLuser @other +users |
| 93 |
|
| 94 |
in this case, as in case b), HUB sets LLL's bit for #channel, |
| 95 |
so it knows that that LLL knows about that channel now. |
| 96 |
|
| 97 |
When LLL gets a SJOIN from its hub that includes in the userlist one of |
| 98 |
LLL's local users, it interprets that that validates a join. If LLLuser |
| 99 |
has ops in that list, then LLL sends: |
| 100 |
|
| 101 |
[LLL --> LLLuser] :HUB MODE #channel +o LLLuser |
| 102 |
|
| 103 |
If not, it just skips that nick from the list. In either case, it |
| 104 |
processes the rest of the SJOIN information (modes, other nicks) by the |
| 105 |
usual SJOIN rules. |
| 106 |
|
| 107 |
|
| 108 |
1.2) Bursts |
| 109 |
|
| 110 |
The beauty of this is that, with the rules above, channel bursts get |
| 111 |
avoided, without the need to do anything more. |
| 112 |
|
| 113 |
When LLL and HUB connect to each other, LLL sends a channel burst as |
| 114 |
usual; HUB doesn't. By the rules above, HUB will reply to each first |
| 115 |
LLL's SJOIN for a channel with a SJOIN back with its own info. So at the |
| 116 |
end of the burst, LLL has been put up to date with all the channels it |
| 117 |
needs to know about. |
| 118 |
|
| 119 |
|
| 120 |
1.3) Parts, Kicks and Modes |
| 121 |
|
| 122 |
When one of LLL's clients (say, LLLuser) leaves a channel, or is kicked |
| 123 |
out of it, LLL needs to check if that was the last of its clients for |
| 124 |
that channel. |
| 125 |
|
| 126 |
If that is the case, then LLL needs to inform HUB that it no longer holds |
| 127 |
#channel, and destroy its local information about #channel: |
| 128 |
|
| 129 |
[LLL -> HUB] :LLL DROP #channel |
| 130 |
|
| 131 |
Upon receiving a "DROP" command from a Lazy Leaf, the Hub just clears |
| 132 |
the Lazy Leaf's bit on that channel. |
| 133 |
|
| 134 |
Alternatively, a Lazy Leaf could decide to cache channels even without |
| 135 |
having any clients on them. All it has to do is not send the "DROP" |
| 136 |
command to its hub. |
| 137 |
|
| 138 |
For MODE commands coming from the rest of the net and related to |
| 139 |
#channel, HUB only needs to pass them to LLL if LLL's bit is set for |
| 140 |
#channel. |
| 141 |
|
| 142 |
For MODE changes related to #channel and done by local users on LLL, |
| 143 |
LLL just passes them as usual to HUB. |
| 144 |
|
| 145 |
For the special "MODE #channel" query, done on LLL, for a channel that |
| 146 |
doesn't exist on LLL, this must be routed through HUB: |
| 147 |
|
| 148 |
[LLL --> HUB] :LLLuser MODE #channel |
| 149 |
[HUB --> LLL] :HUB (numeric) #channel modes |
| 150 |
|
| 151 |
|
| 152 |
2) Nicks |
| 153 |
|
| 154 |
Nicks are simpler, because they are atomic, there is no list associated |
| 155 |
with them. |
| 156 |
|
| 157 |
Again, the hub needs to know, for each nick, which of its Lazy Leaves |
| 158 |
know of it. This can be done with the same 32-bit bitmask as with |
| 159 |
servers. For each user, the associated bit is 1, unless a NICK command |
| 160 |
has been sent or received for that user, on the given Lazy Leaf link. |
| 161 |
|
| 162 |
Once again too, the connect burst gets reduced to just the smaller side: |
| 163 |
the Lazy Leaf dumps its user base on the hub, but not the other way |
| 164 |
round. |
| 165 |
|
| 166 |
When a Lazy Leaf gets a request from one of its local clients, that |
| 167 |
relates to a nick LLL doesn't have, this must be routed through HUB. |
| 168 |
|
| 169 |
|
| 170 |
2.1) WHOIS |
| 171 |
|
| 172 |
For simplicity, we could kill multiple-destination WHOIS, if that's not |
| 173 |
already done, and all kinds of WHOIS *pattern*. |
| 174 |
|
| 175 |
When LLLuser does "WHOIS somenick", if the nick is known to LLL, it |
| 176 |
replies normally. If it isn't, then LLL routes it to HUB: |
| 177 |
|
| 178 |
[LLL --> HUB] :LLLuser WHOIS Somenick |
| 179 |
|
| 180 |
HUB replies with the usual numeric, and also with a burst-style NICK |
| 181 |
introduction, so that from that point on LLL knows about Somenick. HUB |
| 182 |
also sets LLL's bit for Somenick. |
| 183 |
|
| 184 |
[HUB --> LLL] :HUB (numerics) WHOIS info |
| 185 |
[HUB --> LLL] NICK nickTS Somenick HopCount Umode ...... |
| 186 |
|
| 187 |
|
| 188 |
2.2) NOTIFY and USERHOST |
| 189 |
|
| 190 |
These all take lists of users; for a NOTIFY or USERHOST on LLL from one |
| 191 |
of its users, the server checks if *all* of the nicks involved are |
| 192 |
known. If at least one isn't, then the request must be passed as such |
| 193 |
to HUB. HUB then replies to the client, and also sends a NICK |
| 194 |
introduction for each client that LLL didn't previously have. |
| 195 |
|
| 196 |
Note: this kind of sucks, because most NOTIFY lines will tend to include |
| 197 |
a nick or two that isn't on IRC at the moment, which means they will be |
| 198 |
relayed. With almost every client out there having NOTIFY, this might |
| 199 |
well nullify the whole advantage of nick laziness. Or maybe not. |
| 200 |
Someone needs to do some math on it, or some testing, or both. |
| 201 |
|
| 202 |
|
| 203 |
2.3) PRIVMSG and NOTICE |
| 204 |
|
| 205 |
When LLLuser sends "PRIVMSG somenick :message", this must be sent to |
| 206 |
HUB, even if somenick isn't known locally. HUB will figure it out, |
| 207 |
and possibly send a numeric back. |
| 208 |
|
| 209 |
Same for NOTICE. |
| 210 |
|
| 211 |
|
| 212 |
2.4) Anything else??? |
| 213 |
|
| 214 |
We've missed a bunch here... NAMES, WHO, TRACE, and probably others I |
| 215 |
can't think of. |
| 216 |
|
| 217 |
NAMES actually belongs to channels, and might as well not get routed |
| 218 |
(just don't reply) if a LLLUser tries a NAMES for a #channel that it |
| 219 |
isn't on, and that LLL doesn't have any info on. |
| 220 |
|
| 221 |
for WHO, if there's a pattern, just pass the entire command to HUB and |
| 222 |
let it reply to LLLUser through the link (without introducing any extra |
| 223 |
NICKs here). |
| 224 |
|
| 225 |
for TRACE, if the nick is locally unknown, just pass the thing to HUB |
| 226 |
and let it deal with it. |
| 227 |
|
| 228 |
|
| 229 |
4) Avoiding Desyncs |
| 230 |
|
| 231 |
There is one particularly treacherous potential desync: a Lazy Leaf is |
| 232 |
convinced that it has authoritative information about a channel, but its |
| 233 |
hub is convinced that the leaf doesn't. The hub doesn't keep sending |
| 234 |
new information, so the leaf's info grows stale, but it keeps acting on |
| 235 |
it, which eventually leads to wrong decisions. |
| 236 |
|
| 237 |
It is important that the protocol ensures that such desyncs are |
| 238 |
impossible. There should also be periodic cleanup, whereby a Lazy Leaf |
| 239 |
scans its own channel-user list, and deletes its own information about |
| 240 |
any channel on which it doesn't have any local users (and complains to |
| 241 |
its opers about it, because that should never happen). |