Created
September 28, 2021 21:09
-
-
Save nillkitty/3bc5194be4d6f4f94a23860d8db9131d to your computer and use it in GitHub Desktop.
IRC - Inter-network linking proposal (2007)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Internetwork Linking | |
© 2007 Alexis (evil.princess.spike@gmail.com) | |
Introduction | |
This is my answer to the problem of hundreds of tiny IRC networks that want to peer | |
but never get anywhere. Hopefully it may also someday become a viable solution to the | |
problem of having more than one IRC network. (imagine being on EFnet and being able to | |
/join #DALnet:DAL !!) | |
Abstract | |
INL (Internetwork linking) is a way for multiple networks (of servers that contain consistantly | |
stateful information about each other) to link to other networks without: | |
1. having to update and be updated about any state changes in the other network | |
(the main issue stunting IRC scalability), and... | |
2. any of the namespaces from either network (nicknames, channel names) colliding between | |
networks. i.e. networks A and B that are internetworkly linked may both contain a nickname | |
of 'bob'. | |
The Link | |
The method for describing the configuration of the internetwork link is implementation dependant | |
and should be similar to a link block or c:line. | |
Features and Restrictions | |
Given two networks, ANet and BNet, connected with internetwork linking, the following should be | |
possible: | |
1. All of the nicknames, channel names, server names, etc on ANet may be used on BNet without | |
colliding. | |
2. Any user on ANet may send a PRIVMSG or NOTICE to any user on BNet (and vice versa). | |
3. Any user on ANet may join any channel on BNet (and vice versa). | |
4. A channel may be created that exists as a single entity across both ANet and BNet, | |
the prefix for this is '='. | |
5. ANet and BNet may connect in multiple locations (each server may only have one connection | |
to the same network (e.g. a server can't have 2 connections to the same foreign network.)). | |
Data to the foreign network should be sent on the closest link, and data can be recieved on any | |
link and should be routed to the appropriate place using the local server tree. | |
The following are scenarios that must be in place to restrict control and keep anarchy from forming: | |
1. A user on ANet cannot in any way consume any of BNet's resources. It cannot tie up a nickname, | |
channel name, or any other label (it cannot jupe) on BNet, with the exception of a channel that | |
exists across all linked networks, in which case it exists just as much on ANet as it does on BNet. | |
2. An oper on ANet cannot harm (kill, shun, change properties of) a user on BNet unless the | |
link between ANet and BNet honors the statuses of opers across the network link, rather a more | |
viable solution is... | |
3. The ability will exist for opers on ANet to "exile" BNet users, keeping them from causing any | |
event on ANet. Any messages to ANet users from an exiled BNet user are rejected, any attempt to | |
join an ANet channel is denied. Exiled users may join internetwork channels, but the data on their | |
presence will not cross network lines, and thus they will be invisible to ANet users. | |
4. A mechanism will exist to add the addition of network name to a mask so that an entire network | |
can be banned from a channel, added to silence (server side ignore), etc. | |
5. A user cannot create a channel on a foreign network. (Users can only create channels on their | |
home network, or create universal channels). | |
Client Side Protocol | |
For each linked network, a short name (mnemonic) will exist for each linked network. In our | |
example, ANet is "A", and BNet is "B". | |
Identifiers that cross network lines will have the suffix of ":<mnemonic>". Channel naming must be | |
altered to not allow the creation of channels that have a colon in the name. | |
User to User messaging | |
Example: | |
Bob on A does: PRIVMSG john:b :hey! | |
John on B sees: :bob:A!user@host PRIVMSG john :hey! | |
John on B does: PRIVMSG bob:a :yo! | |
Bob on A sees: :john:B!foo@bar PRIVMSG bob :yo! | |
Note that the target does not contain the network title. | |
The same principal is extended to 'secure' (nick@server) messaging: | |
Bob on A does: PRIVMSG john:b@ca.b.net:b :hey! | |
John on B sees: :bob:A!user@host PRIVMSG john@ca.b.net :hey! | |
Note that the network identifier is still required because ANet can also have | |
a server named 'ca.b.net'. | |
And if the nick doesn't exist: | |
Bob on A does: PRIVMSG wheee:b :hi! | |
Reply: :gateway.b.net:B 401 bob wheee:b :No such nick/channel | |
Note that the error came from the BNet server, since ANet has no clue what | |
users exist on BNet. | |
Whois | |
Example: | |
Bob on A does: WHOIS john:b | |
Reply: :gateway.b.net:B 311 bob john:b foo bar * :johnny! | |
:gateway.b.net:B 319 bob john:b :@#mirc:b #foo #foo:b +#rawr:b | |
:gateway.b.net:B 312 bob john:b ca.b.net:b :California B Server (on network BNet) | |
:gateway.b.net:B 318 bob john:b :End of WHOIS list | |
Note that since information about john:b is not propigated to and kept track of by ANet, | |
the WHOIS information actually is relayed to and comes from the first BNet server in the | |
link between ANet and BNet. | |
Channels | |
Example: | |
Bob on A does: JOIN #foo:b | |
Reply: :bob!user@host JOIN #foo:b | |
:gateway.b.net:B 332 bob #foo:B :This topic sucks | |
:gateway.b.net:B 333 bob #foo:B john:b 1923393201 | |
:gateway.b.net:B 353 bob = #foo:b :@patti:B bob +john:B | |
:gateway.b.net:B 366 bob #foo:B :End of NAMES list | |
:patti:B!w00t@host PRIVMSG #foo:B :hey, bob from anet! | |
Bob on A does: PRIVMSG #foo:B :hey | |
Reply: :gateway.b.net:B 404 bob #foo:B :Cannot send to channel (+m) | |
Bob on A does: PRIVMSG patti:b :voice plz | |
Reply: :patti:B!w00t@host MODE #foo:B +v bob | |
But from patti's point of view: | |
:bob:A!user@host JOIN #foo | |
PRIVMSG #foo :hey, bob from anet! | |
:bob:A!user@host PRIVMSG patti :voice plz | |
MODE #foo +v bob:a | |
:patti!w00t@host MODE #foo +v bob:A | |
NAMES #foo | |
:server.b.net 353 patti = #foo :@patti +john bob:A | |
:server.b.net 366 patti #foo :End of NAMES list | |
Patti can ban ANet users very simply: | |
MODE #foo +b *:A!*@* | |
And she can not have to deal with bob's msgs either: | |
SILENCE +*:A | |
Server Protocol | |
The internetwork link is created by connecting on an open port and using the NETWORK message | |
sequence: | |
NETWORK <mnemonic> <protocol version> <network name> :<network info> | |
-> PASS :poop | |
-> NETWORK B 4300 BNet :The land of a thousand channels | |
In which case, if correct, the other server echos back their information: | |
<- PASS :poopies | |
<- NETWORK A 4300 ANet :The land of hundreds of opers | |
Each server should then burst their gateway server's information | |
-> SERVER gateway.b.net 0 :ANet<->BNet Gateway | |
<- SERVER gateway.a.net 0 :BNet<->ANet Gateway | |
Then a list of any other networks connected | |
-> :gateway.b.net NETWORK C 4300 CNet :The badest network of them all! | |
At this point, whether Anet's servers want to allow ANet users to use the :C suffix is up to them, | |
but the servers at least know it exists and to possibly expect messages from it. | |
Next up is the periodic stats adjustment which takes the form: | |
COUNT <mnemonic> <total users> <invisible users> <channels> <opers> <unknown> | |
-> :gateway.b.net COUNT B 403 33 83 2 4 | |
This count is only for network B. ANet's server should add those numbers to it's own ANet counts | |
to arrive at a global LUSERS count. The COUNT message is periodically updated (but not everytime it | |
changes). It's a rough estimate of the size of the network. | |
Then the link bursts an NUHDATA blocks which is basically a list of all the nick!user@host triplets | |
for any user that's in any universal channel (since every server will need to know at least the | |
nick!user@host in case of a NAMES on the channel). | |
-> :gateway.b.net NUHDATA bob:B!user@host janet:B!a@b marc:B!moof@yawn rover:C!heh@blah etc.. | |
This is followed by a list of any universal (internetwork) channels that exist at the time, with all | |
users having all network suffixes. | |
-> :gateway.b.net SJOIN 1080222029 =cafe +ntml 5000 :@bob:B janet:B marc:B rover:C coke:B marty:B | |
Any channel collisions with universal (=) channels are done in the normal way with SJOIN's and TS's. | |
This is the end of the burst: | |
-> :gateway.b.net EOB B 1023393200 | |
<- :gateway.a.net EOB A 1023393201 | |
And we're happy. | |
More Server Protocol | |
The server protocol for internetwork links is very easy because almost no state information is sent, | |
only event information. | |
Privmsg, Notice | |
:nick:network PRIVMSG target:network :message | |
Numeric Reply | |
:server:network 000 target:network <data> | |
NOTE: remember that numerics (and replies in general) to foreign users should be formatted as if | |
the information was foreign to them. For example, when sending RPL_WHOISUSER, be sure that the | |
queried user's nick is suffixed with the native network mnemonic as illistrated above. | |
A gateway server recieving any incoming numeric (or anything that gets forwarded to users) must | |
strip off the native suffix if it's for that network. For example, a user on Anet should never | |
recieve a message with any parameter suffixed with an ":A", and any parameter with a BNet parameter | |
should be suffixed with ":B". | |
Join | |
When the client does a /JOIN #channel:B, the message is sent to the B's | |
gateway server as: | |
-> :nick:A &JOIN #channel:B | |
Which is then replied to with either an error numeric or the following sequence: | |
<- :gateway.b.net:B 474 nick:A #channel:B :Cannot join channel (+b) | |
or | |
<- :gateway.b.net:B JOINOK nick:A #channel:B | |
This generates the ":nick!user@host JOIN #channel:B" message to nick on nick's server. | |
<- :gateway.b.net:B 332 nick:A #channel:B :This is the topic, etc... | |
RPL_TOPICWHOTIME | |
RPL_NAMREPLY | |
RPL_ENDOFNAMES | |
etc... | |
Part | |
Parts should be assumed. If a client sends a PART message for a channel that they're in (Anet would | |
keep track of the fact that bob is in a channel on BNet. It just wouldn't care about any of that | |
channel's details), the PART should be assumed, sent to the client, and then sent to the gateway. | |
:nickname:network PART #channel:network [:message] | |
:nickname:network PART =channel [:message] | |
Kick | |
Kicks are forwarded as requests (&KICK) and sent back as kick events (KICK). | |
Mode | |
Channel modes are forwarded as requests (&MODE) and sent back as mode events (MODE). | |
Topic | |
Topic changes (and requests) are sent as requests (&TOPIC) and sent back as topic events (TOPIC). | |
Kill | |
Kills are sent as kill requests (&KILL) and if the kill is honored, it comes back as a quit | |
event (QUIT) and a kill-related oper notice (SENDSNO/SMO/WALLOPS/etc). | |
Who/Whois/Names/Topic Request/Mode Request/Remote Stats | |
These are handled as if they were done to a remote server; sent as the original command, and replied | |
to in numerics. | |
Userhost/Ison | |
These are a special case because multiple queries can exist that need to come back to the user | |
as a single numeric. Here is the method of doing this: | |
Assume the example "/ison bob carla nick john:B ralph:B mike:C". The local server first | |
looks at the nicks for the native network. Carla is offline, but bob and nick are online, | |
so the query turns into: | |
":origin:A ISON A :bob:A nick:A john:B ralph:B mike:C" and sent to B's gateway. | |
B picks it up.. and sees that ralph is online, but john isn't. The query is turned into: | |
":origin:A ISON A,B :bob:A nick:A ralph:B mike:C" and sent to C's gateway. | |
C picks it up, and mike is online. So it dispatches this back to B: | |
":gateway.c.net:C 302 origin:A :bob nick ralph:B mike:C" | |
Because, according to "origin:A" prefix, the origin is on network A, so it strips off the :A suffixes | |
and sends it as numeric form. | |
C knows that the query is done because every network listed was behind the link that it came from | |
(or itself). had there been a D linked to C, it would add C to the first parameter and send it on | |
where it would terminate. Had there been a D linked to B, it would add C to the parameter and send | |
it back to B, who would know that it already went to C and then send it to D where it would terminate. | |
Discovery | |
Clients can see what networks are linked by doing a standard LINKS. The output would look like: | |
<- :server.a.net 364 bob server.a.net server.a.net :0 A Net Server | |
<- :server.a.net 364 bob gateway.a.net server.a.net :1 BNet<->ANet Gateway | |
<- :server.a.net 364 bob gateway.b.net:B gateway.a.net :2 ANet<->BNet Gateway | |
<- :server.a.net 364 bob *:B gateway.b.net:B :* BNet - Land of a thousand channels | |
<- :server.a.net 364 bob *:C *:B :* CNet - Baddest network of all! | |
<- :server.a.net 365 bob :End of LINKS | |
Note that several things should be noted here: | |
1. Since A knows about B's gateway server (gateways exchange SERVER messages), it appears in the | |
links list as the gateway, with "=B" (The B network) stemming from it. Since A also knows about C | |
(via the propigated NETWORK message), it knows that it branches off of B. Networks show up in the | |
links reply as being *:<mnemonic> and having a hopcount of "*". | |
2. If the user wants a complete listing of Bnet, they can always do a /links *:B which would match | |
the first B server, gateway.b.net:B, and forward the LINKS request to that. | |
And for those networks that don't set all users +i: | |
If a user does a /WHO *:<mnemonic> query or the like, it might be nice to forward it to <mnemonic> | |
network as :nick WHO * so they can learn about the users on that network (since the local network | |
doesn't have a complete list to use). | |
Foreign users should not show up in WHO queries that do not have a :suffix on the end, by default, | |
(since not all of the users are known to respond with a close query). | |
Other Considerations | |
1. For obvious reasons, a foreign nick shouldn't be allowed to be added to a WATCH list. | |
2. Since network A doesn't know about any user on network B unless it has to deal with it, | |
the only possible way for a G-line to work is to simply ignore a g-lined user's presence when | |
relaying messages. The G-line also acts as an exile on the user. However g-lines and exiles | |
MUST NOT block the JOIN/PART/QUIT/KICK/KILL messages for such users, as this could break state | |
when a user is g-lined/exiled while already on a channel, also a channel owner has the right | |
to see any foreign users in their channel and kick them, etc. Exiled foreign users also can't be stopped | |
from viewing information that originates locally because it's only sent to the foreign network once | |
and blocking it will also block it from reaching legitimate users on that network. Another option is | |
to send out a KILL for the user when the user's triplet becomes known and matches a g-line, however | |
not all internetwork links will honor cross-network kills. | |
3. An oper on a network may destroy channels that exist on that network, removing all users from it | |
in the rare case of desynch. Such a destruction should be propigated as "KILL #channel:network", and | |
on the client side it should look like a mass-kick. | |
4. The only time network A stores any data on network B's user is if they're in a cross-network channel, | |
and even then it's just the nick!user@host and channel status. An oper should have the ability to | |
/REFRESH the user, removing the data locally and sending a REFRESH message for the user to their native | |
network, requestinga NUHDATA update for the user in the rare case of a desynch. | |
5. It may be a good idea for a server to cache a list of nicknames that have been bounced with a 401 | |
(ERR_NOSUCHNICK) in the last x seconds (probably between 1 and 5) as to avoid having to send huge | |
floods of information out of the network gateway and have it bounce in case someone is testing some type | |
of popup or script on a nick that doesn't exist or the equivilent. Probably also the same for nonexistant | |
foreign channels (since local users can't create remote channels). | |
6. It might also be a good idea to create some type of class of message that encompasses an entire network, | |
just in case. Probably /WALLNETS <network mask> <message>. | |
7. It's an even better idea to have a class of message that sends to opers of a foreign network, | |
such as /FOPERWALL <network mask> <message> (foreign oper write all) and one to communicate with all | |
online opers (possibly /UNOPS (universal opers)). of course any of this can be blocked at the network | |
link gateway. | |
8. JOIN should probably also be extended to work for 0:<network>, parting all channels on a given | |
network. | |
9. It's probably really good idea to have time between gateways synchronized perfectly. | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment