0
0
Fork 0
mirror of https://github.com/matrix-construct/construct synced 2024-12-30 17:34:04 +01:00
construct/doc/technical/README.TSora

331 lines
15 KiB
Text
Raw Normal View History

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 <carlo@runaway.xs4all.nl>. 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 <TS_CURRENT> <TS_MIN> <STANDALONE> :<UTC-TIME>
just after "SERVER", where <STANDALONE> is 1 if we're connected to
more TSora servers, and 0 if not, and <UTC-TIME> is our idea of the
current UTC time, fixed with the delta.
. When we receive a "SVINFO <x> <y> <z> :<t>" line from a connecting
server, we ignore it if TS_CURRENT<y or x<TS_MIN, otherwise we
set a flag remembering that that server is TS-aware, remember the TS
version to use with it (min(TS_CURRENT, x)). Additionally, if this is
our first connected TS server, we set our delta to t-<OUR_UTC> if
z==0, and to (t-<OUR_UTC>)/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 <nick> <hops> <TS> <umode> <user> <host> <server> :<ircname>
The umode is a + followed by any applying usermodes.
The format for a nick-change NICK line is:
:<oldnick> NICK <newnick> :<TS>
. 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 <TS> #<channel> <modes> :[@][+]<nick_1> ... [@][+]<nick_n>
The fields have the following meanings:
* <TS> is the timestamp for the channel
* <modes> 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 <mode> field.
An extra ad-hoc restriction is imposed on SJOIN messages, to simplify
processing: if a channel has ops, then the first <nick> 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:
+ if we have ops or the SJOIN doesn't op anyone, SJOIN propagated
with our own TS.
+ otherwise, TS set to 0 and SJOIN propagated with 0.
* received-TS > 0, own-TS = 0:
+ if the SJOIN ops someone or we don't have ops, set our TS to the
received TS and propagate.
+ otherwise, propagate with TS = 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. 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.