Skip to content

Instantly share code, notes, and snippets.

@nillkitty
Created September 28, 2021 21:09
Show Gist options
  • Save nillkitty/3bc5194be4d6f4f94a23860d8db9131d to your computer and use it in GitHub Desktop.
Save nillkitty/3bc5194be4d6f4f94a23860d8db9131d to your computer and use it in GitHub Desktop.
IRC - Inter-network linking proposal (2007)
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