Use a smarter algorithm for choosing MTU discovery probe sizes.
Currently, tinc uses a naive algorithm for choosing MTU discovery probe sizes, picking a size at random between minmtu and maxmtu. This is of course suboptimal - since the behavior of probes is deterministic (assuming no packet loss), it seems likely that using a non-deterministic discovery algorithm will not yield the best results. Furthermore, the randomness introduces a lot of variation in convergence times. The random solution also suffers from pathological cases - since it's using a uniform distribution, it doesn't take into account the fact that it's often more interesting to send small probes rather than large ones, because getting replies is the only way we can make progress (assuming the worst case scenario in which the OS doesn't know anything, therefore keeping maxmtu constant). This can lead to absurd situations where the discovery algorithm is close to the real MTU, but can't get to it because the random number generator keeps generating numbers that are past it. The algorithm implemented in this patch aims to improve on the naive random algorithm. It is organized around "cycles" of 8 probes; the sizes of the probes decrease as we go through the cycle, thus making sure the algorithm can cover lots of ground quickly (in case we're far from actual MTU), but also examining the local area (in case we're close to actual MTU). Using cycles ensures that the algorithm will "go back" to large probes to better cover the new interval and to protect against packet loss. For the probe size itself, various mathematical models were simulated in an attempt to find the one that converges the fastest; it has been determined that using an exponential based on the size of the remaining interval was the most effective option. The exponential is adjusted with a magic multiplier fine-tuned to make tinc jump to the "most interesting" (i.e. 1400+) section as soon as discovery starts. Simulations indicate that assuming no packet loss and no help from the OS (i.e. maxmtu stays constant), this algorithm will typically converge to the *exact* MTU value in less than 10 probes, and will get within 8 bytes in less than 5 probes, for actual MTUs between 1417 and ~1450 (which is the range the algorithm is fine-tuned for). In contrast, the previous algorithm gives results all over the place, sometimes taking 30+ probes to get in the ballpark. Because of the issues with the distribution, the previous algorithm sometimes never gets to the precise MTU value within any reasonable amount of time - in contrast, the new algorithm will always get to the precise value in less than 30 probes, even if the actual MTU is completely outside the optimized range.
This commit is contained in:
parent
c22560ae32
commit
24d28adf64
3 changed files with 25 additions and 7 deletions
|
@ -250,7 +250,7 @@ endif
|
||||||
tinc_LDADD = $(READLINE_LIBS) $(CURSES_LIBS)
|
tinc_LDADD = $(READLINE_LIBS) $(CURSES_LIBS)
|
||||||
sptps_speed_LDADD = -lrt
|
sptps_speed_LDADD = -lrt
|
||||||
|
|
||||||
LIBS = @LIBS@
|
LIBS = @LIBS@ -lm
|
||||||
|
|
||||||
if TUNEMU
|
if TUNEMU
|
||||||
LIBS += -lpcap
|
LIBS += -lpcap
|
||||||
|
|
|
@ -40,6 +40,7 @@
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
#ifdef HAVE_MINGW
|
#ifdef HAVE_MINGW
|
||||||
#include <w32api.h>
|
#include <w32api.h>
|
||||||
|
|
|
@ -922,13 +922,30 @@ static void try_mtu(node_t *n) {
|
||||||
if(n->maxmtu + 8 < MTU)
|
if(n->maxmtu + 8 < MTU)
|
||||||
send_udp_probe_packet(n, n->maxmtu + 8);
|
send_udp_probe_packet(n, n->maxmtu + 8);
|
||||||
} else {
|
} else {
|
||||||
/* Probes are sent with random sizes between the
|
/* Decreasing the number of probes per cycle might make the algorithm react faster to lost packets,
|
||||||
lower and upper boundaries for the MTU thus far discovered. */
|
but it will typically increase convergence time in the no-loss case. */
|
||||||
int len = n->maxmtu;
|
const length_t probes_per_cycle = 8;
|
||||||
if(n->minmtu < n->maxmtu)
|
|
||||||
len = n->minmtu + 1 + rand() % (n->maxmtu - n->minmtu);
|
|
||||||
send_udp_probe_packet(n, MAX(len, 64));
|
|
||||||
|
|
||||||
|
/* This magic value was determined using math simulations.
|
||||||
|
It will result in a 1339-byte first probe, followed (if there was a reply) by a 1417-byte probe.
|
||||||
|
Since 1417 is just below the range of tinc MTUs over typical networks,
|
||||||
|
this fine-tuning allows tinc to cover a lot of ground very quickly. */
|
||||||
|
const float multiplier = 0.982;
|
||||||
|
|
||||||
|
const float cycle_position = probes_per_cycle - (n->mtuprobes % probes_per_cycle) - 1;
|
||||||
|
const length_t minmtu = MAX(n->minmtu, 64);
|
||||||
|
const float interval = n->maxmtu - minmtu;
|
||||||
|
|
||||||
|
/* The core of the discovery algorithm is this exponential.
|
||||||
|
It produces very large probes early in the cycle, and then it very quickly decreases the probe size.
|
||||||
|
This reflects the fact that in the most difficult cases, we don't get any feedback for probes that
|
||||||
|
are too large, and therefore we need to concentrate on small offsets so that we can quickly converge
|
||||||
|
on the precise MTU as we are approaching it.
|
||||||
|
The last probe of the cycle is always 1 byte in size - this is to make sure we'll get at least one
|
||||||
|
reply per cycle so that we can make progress. */
|
||||||
|
const length_t offset = powf(interval, multiplier * cycle_position / (probes_per_cycle - 1));
|
||||||
|
|
||||||
|
send_udp_probe_packet(n, minmtu + offset);
|
||||||
if(n->mtuprobes >= 0)
|
if(n->mtuprobes >= 0)
|
||||||
n->mtuprobes++;
|
n->mtuprobes++;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue