| 1 |
knight |
31 |
$Id$ |
| 2 |
adx |
30 |
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). |