Protocol changes for +TSora --------------------------- Note: The protocols described here implement TimeStamps on IRC channels and nicks. The idea of IRC TimeStamps was started on Undernet, and first implemented by Run . The protocols used here are not exactly the same as the ones used on Undernet; the nick-kill handling is very similar and must be credited to Run, while the "TimeStamped channel description" protocol is quite different. TSora servers keep track of which version of the TS protocol (if any) their neighboring servers are using, and take it into account when sending messages to them. This allows for seamless integration of TS servers into a non-TS net, and for upgrades of the protocol. Each server knows which is the lowest and the highest version of the TS protocol it can interact with; currently both of these are set to 1: #define TS_CURRENT 1 /* the highest TS ver we can do */ #define TS_MIN 1 /* the lowest TS ver we can do */ Timings and TS versions: ======================== . Keep a 'delta' value to be added to the result of all calls to time(), initially 0. . Send a second argument to the PASS command, ending in the 'TS' string. . Send a SVINFO : just after "SERVER", where is 1 if we're connected to more TSora servers, and 0 if not, and is our idea of the current UTC time, fixed with the delta. . When we receive a "SVINFO :" line from a connecting server, we ignore it if TS_CURRENT if z==0, and to (t-)/2 if z!=0. The SVINFO data is kept around until the server has effectively registered with SERVER, and used *after* sending our own SVINFO to that server. Explanations: Servers will always know which of their directly-linked servers can do TS, and will use the TS protocol only with servers that do understand it. This makes it possible to switch to full TS in just one code-replacement step, without incompatibilities. As long as not all servers are TS-aware, the net will be divided into "zones" of linked TS-aware servers. Channel modes will be kept synchronized at least within the zone in which the channel was created, and nick collisions between servers in the same zone will result in only one client being killed. Time synchronization ensures that servers have the same idea of the current time, and achieves this purpose as long as TS servers are introduced one by one within the same 'zone'. The merging of two zones cannot synchronize them completely, but it is to be expected that within each zone the effective time will be very close to the real time. By sending TSINFO after SERVER rather than before, we avoid the extra lag created by the identd check on the server. To be able to send immediately a connect burst of either type (TS or not), we need to know before that if the server does TS or not, so we send that information with PASS as an extra argument. And to avoid being incompatible with 2.9 servers, which check that this second argument begins with "2.9", we check that it *ends* with "TS". The current time is only used when setting a TS on a new channel or nick, and once such a TS is set, it is never modified because of synchronization, as it is much more important that the TS for a channel or nick stays the same across all servers than that it is accurate to the second. Note that Undernet's 2.8.x servers have no time synchronization at all, and have had no problems because of it - all of this is more to catch the occasional server with a way-off clock than anything. NICK handling patches (anti-nick-collide + shorter connect burst): ================================================================== . For each nick, store a TS value = the TS value received if any, or our UTC+delta at the time we first heard of the nick. TS's are propagated to TS-aware servers whenever sending a NICK command. . Nick changes reset the TS to the current time. . When sending a connect burst to another TS server, replace the NICK/USER pair with only one NICK command containing the nick, the hopcount, the TS, the umode, and all the USER information. The format for a full NICK line is: NICK : The umode is a + followed by any applying usermodes. The format for a nick-change NICK line is: : NICK : . When a NICK is received from a TS server, that conflicts with an existing nick: + if the userhosts differ or one is not known: * if the timestamps are equal, kill ours and the old one if it was a nick change * if the incoming timestamp is older than ours, kill ours and propagate the new one * if the incoming timestamp is younger, ignore the line, but kill the old nick if it was a nick change + if the userhosts are the same: * if the timestamps are equal, kill ours and the old one if it was a nick change * if the incoming timestamp is younger, kill ours and propagate the new one * if the incoming timestamp is older, ignore the line but kill the old nick if it was a nick change . When a NICK is received from a non-TS server that conflicts with an existing nick, kill both. . Do not send "Fake Prefix" kills in response to lines coming from TS servers; the sanitization works anyway, and this allows the "newer nick overruled" case to work. Explanations: The modified nick-introduction syntax allows for a slightly shorter connect-burst, and most importantly lets the server compare user@host's when determining which nick to kill: if the user@host is the same, then the older nick must be killed rather than the newer. When talking to a non-TS server, we need to behave exactly like one because it expects us to. When talkign to a TS server, we don't kill the nicks it's introducing, as we know it'll be smart enough to do it itself when seeing our own introduced nick. When we see a nick arriving from a non-TS server, it won't have a TS, but it's safe enough to give it the current time rather than keeping it 0; such TS's won't be the same all across the network (as long as there is more than one TS zone), and when there's a collision, the TS used will be the one in the zone the collision occurs in. Also, it is important to note that by the time a server sees (and chooses to ignore) a nick introduction, the introducing server has also had the time to put umode changes for that nick on its queue, so we must ignore them too... so we need to ignore fake-prefix lines rather than sending kills for them. This is safe enough, as the rest of the protocol ensures that they'll get killed anyway (and the Undernet does it too, so it's been more than enough tested). Just for an extra bit of compatibility, we still kill fake prefixes coming from non-TS servers. This part of the TS protocol is almost exactly the same as the Undernet's .anc (anti-nick-collide) patches, except that Undernet servers don't add usermodes to the NICK line. TimeStamped channel descriptions (avoiding hacked ops and desynchs): ==================================================================== . For each channel, keep a timestamp, set to the current time when the channel is created by a client on the local server, or to the received value if the channel has been propagated from a TS server, or to 0 otherwise. This value will have the semantics of "the time of creation of the current ops on the channel", and 0 will mean that the channel is in non-TS mode. A new server protocol command is introduced, SJOIN, which introduces a full channel description: a timestamp, all the modes (except bans), and the list of channel members with their ops and voices. This command will be used instead of JOIN and of (most) MODEs both in connect bursts and when propagating channel creations among TS servers. SJOIN will never be accepted from or sent to users. The syntax for the command is: SJOIN # :[@][+] ... [@][+] The fields have the following meanings: * is the timestamp for the channel * is the list of global channel modes, starting with a + and a letter for each of the active modes (spmntkil), followed by an argument for +l if there is a limit, and an argument for +k if there's a key (in the same order they were mentioned in the string of letters). A channel with no modes will have a "+" in that field. A special value of "0" means that the server does not specify the modes, and will be used when more than one SJOIN line is needed to completely describe a channel, or when propagating a SJOIN the modes of which were rejected. * Each nick is preceded by a "@" if the user has ops, and a "+" if the user has a voice. For mode +ov, both flags are used. SJOINs will be propagated (when appropriate) to neighboring TS servers, and converted to JOINs and MODEs for neighboring non-TS servers. To propagate channels for which not all users fit in one SJOIN line, several SJOINs will be sent consecutively, only the first one including actual information in the field. An extra ad-hoc restriction is imposed on SJOIN messages, to simplify processing: if a channel has ops, then the first of the first SJOIN sent to propagate that channel must be one of the ops. Servers will never attempt to reconstruct a SJOIN from JOIN/MODE information being received at the moment from other servers. . For each user on a channel, keep an extra flag (like ops and voice) that is set when the user has received channel ops from another server (in a SJOIN channel description), which we rejected (ignored). Mode changes (but NOT kicks) coming from a TS server and from someone with this flag set will be ignored. The flag will be reset when the user gets ops from another user or server. . On deops done by non-local users, coming from TS servers, on channels with a non-zero TS, do not check that the user has ops but check that their 'deopped' flag is not set. For kicks coming from a TS server, do not check either. This will avoid desynchs, and 'bad' modechanges are avoided anyway. Other mode changes will still only be taken into account and propagated when done by users that are seen as having ops. . When a MODE change that ops someone is received from a server for a channel, that channel's TS is set to 0, and the mode change is propagated. . When a SJOIN is received for a channel, deal with it in this way: * received-TS = 0 or own TS = 0: set own TS to 0, propagate a 0. * received-TS = own-TS: propagate. * received-TS < own-TS: + if the SJOIN ops someone, remove *all* modes (except bans) from the channel and propagate these mode changes to all neighboring non-TS servers, and copy the received TS and propagate the SJOIN. + if the SJOIN does not op anyone and we have ops, propagate with our own TS. + otherwise, copy the received TS and propagate the SJOIN. * received-TS > own-TS: + if the SJOIN does not introduce any ops, process and propagate with our own TS. + if we have ops: for each person the mode change would op, set the 'deopped' flag; process all the JOINs ignoring the '@' and '+' flags; propagate without the flags and with our TS. + if we don't have ops: set our TS to the received one, propagate with the flags. Explanations: This part of the protocol is the one that is most different (and incompatible) with the Undernet's: we never timestamp MODE changes, but instead we introduce the concept of time-stamped channel descriptions. This way each server can determine, based on its state and the received description, what the correct modes for a channel are, and deop its own users if necessary. With this protocol, there is *never* the need to reverse and bounce back a mode change. This is both faster and more bandwith-effective. The end goal is to have a protocol will eventually protect channels against hacked ops, while minimizing the impact on a mixed-server net. In order to do this, whenever there is a conflict between a TS server and a non-TS one, the non-TS one's idea of the whole situation prevails. This means that channels will only have a TS when they have been created on a TS-aware server, and will lose it whenever a server op comes from a non-TS server, or when a non-TS server introduces it without a TS. Also, at most one 'zone' will have a TS for any given channel at any given time, ensuring that there won't be any deops when zones are merged. However, when TS zones are merged, if the side that has a TS also has ops, then the TS is kept across the whole new zone. Effective protection will only be ensured once all servers run TS patches and channels have been re-created, as there is no way servers can assign a TS to a channel they are not creating (like they do with nicks) without having unwanted deops later. The visible effects of this timestamped channel-description protocol are that when a split rejoins, and one side has hacked ops, the other side doesn't see any server mode changes (just like with Undernet's TS), but the side that has hacked ops sees: * first the first server on the other side deopping and devoicing everyone, and fixing the +spmntkli modes * then other users joining, and getting server ops and voices The less obvious part of this protocol is its behavior in the case that the younger side of a rejoin has servers that are lagged with each other. In such a situation, a SJOIN that clears all modes and sets the legitimate ones is being propagated from one server, and lagged illegitimate mode changes and kicks are being propagated in the opposite direction. In this case, a kick done by someone who is being deopped by the SJOIN must be taken into account to keep the name list in sync (and since it can only be kicking someone who also was on the younger side), while a deop does not matter (and will be ignored by the first server on the other side), and an opping *needs* to be discareded to avoid hacked ops. The main property of timestamped channel descriptions that makes them a very stable protocol even with lag and splits, is that they leave a server in the same final state, independently of the order in which channel descriptions coming from different servers are received. Even when SJOINs and MODEs for the same channel are being propagated in different direction because of several splits rejoining, the final state will be the same, independently of the exact order in which each server received the SJOINs, and will be the same across all the servers in the same zone.