Tinc allows multiple nodes to own the same Subnet, but did not have a sensible
way to decide which one to send packets to. Tinc also did not check the
reachability of nodes when deciding where to route packets to, so it would not
automatically fail over to a reachable node.
Tinc now assigns a weight to each Subnet. The default weight is 10, with lower
weights having higher priority. The Subnets are now internally sorted in the
same way as the kernel's routing table, and the Subnets are search linearly,
skipping those of unreachable nodes. A small cache of recently used addresses
is used to speed up the lookup functions.
in each host configuration file,
if you want to be able to establish a connection with that host.
in each host configuration file,
if you want to be able to establish a connection with that host.
-.It Va Subnet Li = Ar address Ns Op Li / Ns Ar prefixlength
+.It Va Subnet Li = Ar address Ns Op Li / Ns Ar prefixlength Ns Op Li # Ns Ar weight
The subnet which this tinc daemon will serve.
.Nm tinc
tries to look up which other daemon it should send a packet to by searching the appropriate subnet.
The subnet which this tinc daemon will serve.
.Nm tinc
tries to look up which other daemon it should send a packet to by searching the appropriate subnet.
IPv6 subnets are notated like fec0:0:0:1:0:0:0:0/64.
MAC addresses are notated like 0:1a:2b:3c:4d:5e.
IPv6 subnets are notated like fec0:0:0:1:0:0:0:0/64.
MAC addresses are notated like 0:1a:2b:3c:4d:5e.
+.Pp
+A Subnet can be given a weight to indicate its priority over identical Subnets
+owned by different nodes. The default weight is 10. Lower values indicate
+higher priority. Packets will be sent to the node with the highest priority,
+unless that node is not reachable, in which case the node with the next highest
+priority will be tried, and so on.
+
.It Va TCPOnly Li = yes | no Pq no
If this variable is set to yes,
then the packets are tunnelled over the TCP connection instead of a UDP connection.
.It Va TCPOnly Li = yes | no Pq no
If this variable is set to yes,
then the packets are tunnelled over the TCP connection instead of a UDP connection.
connection with that host.
@cindex Subnet
connection with that host.
@cindex Subnet
-@item Subnet = <@var{address}[/@var{prefixlength}]>
+@item Subnet = <@var{address}[/@var{prefixlength}[#@var{weight}]]>
The subnet which this tinc daemon will serve.
Tinc tries to look up which other daemon it should send a packet to by searching the appropiate subnet.
If the packet matches a subnet,
The subnet which this tinc daemon will serve.
Tinc tries to look up which other daemon it should send a packet to by searching the appropiate subnet.
If the packet matches a subnet,
/22. This conforms to standard CIDR notation as described in
@uref{ftp://ftp.isi.edu/in-notes/rfc1519.txt, RFC1519}
/22. This conforms to standard CIDR notation as described in
@uref{ftp://ftp.isi.edu/in-notes/rfc1519.txt, RFC1519}
+A Subnet can be given a weight to indicate its priority over identical Subnets
+owned by different nodes. The default weight is 10. Lower values indicate
+higher priority. Packets will be sent to the node with the highest priority,
+unless that node is not reachable, in which case the node with the next highest
+priority will be tried, and so on.
+
@cindex TCPonly
@item TCPonly = <yes|no> (no)
If this variable is set to yes, then the packets are tunnelled over a
@cindex TCPonly
@item TCPonly = <yes|no> (no)
If this variable is set to yes, then the packets are tunnelled over a
sssp_bfs();
mst_kruskal();
graph_changed = true;
sssp_bfs();
mst_kruskal();
graph_changed = true;
+/* Subnet lookup cache */
+
+static ipv4_t cache_ipv4_address[2];
+static subnet_t *cache_ipv4_subnet[2];
+static bool cache_ipv4_valid[2];
+static int cache_ipv4_slot;
+
+static ipv6_t cache_ipv6_address[2];
+static subnet_t *cache_ipv6_subnet[2];
+static bool cache_ipv6_valid[2];
+static int cache_ipv6_slot;
+
+void subnet_cache_flush() {
+ cache_ipv4_valid[0] = cache_ipv4_valid[1] = false;
+ cache_ipv6_valid[0] = cache_ipv6_valid[1] = false;
+}
+
/* Subnet comparison */
static int subnet_compare_mac(const subnet_t *a, const subnet_t *b)
/* Subnet comparison */
static int subnet_compare_mac(const subnet_t *a, const subnet_t *b)
result = memcmp(&a->net.mac.address, &b->net.mac.address, sizeof(mac_t));
result = memcmp(&a->net.mac.address, &b->net.mac.address, sizeof(mac_t));
+ if(result)
+ return result;
+
+ result = a->weight - b->weight;
+
if(result || !a->owner || !b->owner)
return result;
if(result || !a->owner || !b->owner)
return result;
- result = memcmp(&a->net.ipv4.address, &b->net.ipv4.address, sizeof(ipv4_t));
+ result = b->net.ipv4.prefixlength - a->net.ipv4.prefixlength;
if(result)
return result;
if(result)
return result;
- result = a->net.ipv4.prefixlength - b->net.ipv4.prefixlength;
+ result = memcmp(&a->net.ipv4.address, &b->net.ipv4.address, sizeof(ipv4_t));
+
+ if(result)
+ return result;
+
+ result = a->weight - b->weight;
if(result || !a->owner || !b->owner)
return result;
if(result || !a->owner || !b->owner)
return result;
- result = memcmp(&a->net.ipv6.address, &b->net.ipv6.address, sizeof(ipv6_t));
+ result = b->net.ipv6.prefixlength - a->net.ipv6.prefixlength;
if(result)
return result;
if(result)
return result;
+
+ result = memcmp(&a->net.ipv6.address, &b->net.ipv6.address, sizeof(ipv6_t));
- result = a->net.ipv6.prefixlength - b->net.ipv6.prefixlength;
+ if(result)
+ return result;
+
+ result = a->weight - b->weight;
if(result || !a->owner || !b->owner)
return result;
if(result || !a->owner || !b->owner)
return result;
cp();
subnet_tree = avl_alloc_tree((avl_compare_t) subnet_compare, (avl_action_t) free_subnet);
cp();
subnet_tree = avl_alloc_tree((avl_compare_t) subnet_compare, (avl_action_t) free_subnet);
+
+ subnet_cache_flush();
}
void exit_subnets(void)
}
void exit_subnets(void)
avl_insert(subnet_tree, subnet);
avl_insert(n->subnet_tree, subnet);
avl_insert(subnet_tree, subnet);
avl_insert(n->subnet_tree, subnet);
+
+ subnet_cache_flush();
}
void subnet_del(node_t *n, subnet_t *subnet)
}
void subnet_del(node_t *n, subnet_t *subnet)
avl_delete(n->subnet_tree, subnet);
avl_delete(subnet_tree, subnet);
avl_delete(n->subnet_tree, subnet);
avl_delete(subnet_tree, subnet);
+
+ subnet_cache_flush();
}
/* Ascii representation of subnets */
}
/* Ascii representation of subnets */
{
int i, l;
uint16_t x[8];
{
int i, l;
uint16_t x[8];
- if(sscanf(subnetstr, "%hu.%hu.%hu.%hu/%d",
- &x[0], &x[1], &x[2], &x[3], &l) == 5) {
+ if(sscanf(subnetstr, "%hu.%hu.%hu.%hu/%d#%d",
+ &x[0], &x[1], &x[2], &x[3], &l, &weight) >= 5) {
if(l < 0 || l > 32)
return false;
subnet->type = SUBNET_IPV4;
subnet->net.ipv4.prefixlength = l;
if(l < 0 || l > 32)
return false;
subnet->type = SUBNET_IPV4;
subnet->net.ipv4.prefixlength = l;
+ subnet->weight = weight;
for(i = 0; i < 4; i++) {
if(x[i] > 255)
for(i = 0; i < 4; i++) {
if(x[i] > 255)
- if(sscanf(subnetstr, "%hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx/%d",
+ if(sscanf(subnetstr, "%hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx/%d#%d",
&x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &x[6], &x[7],
&x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &x[6], &x[7],
if(l < 0 || l > 128)
return false;
subnet->type = SUBNET_IPV6;
subnet->net.ipv6.prefixlength = l;
if(l < 0 || l > 128)
return false;
subnet->type = SUBNET_IPV6;
subnet->net.ipv6.prefixlength = l;
+ subnet->weight = weight;
for(i = 0; i < 8; i++)
subnet->net.ipv6.address.x[i] = htons(x[i]);
for(i = 0; i < 8; i++)
subnet->net.ipv6.address.x[i] = htons(x[i]);
- if(sscanf(subnetstr, "%hu.%hu.%hu.%hu", &x[0], &x[1], &x[2], &x[3]) == 4) {
+ if(sscanf(subnetstr, "%hu.%hu.%hu.%hu#%d", &x[0], &x[1], &x[2], &x[3], &weight) >= 4) {
subnet->type = SUBNET_IPV4;
subnet->net.ipv4.prefixlength = 32;
subnet->type = SUBNET_IPV4;
subnet->net.ipv4.prefixlength = 32;
+ subnet->weight = weight;
for(i = 0; i < 4; i++) {
if(x[i] > 255)
for(i = 0; i < 4; i++) {
if(x[i] > 255)
- if(sscanf(subnetstr, "%hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx",
- &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &x[6], &x[7]) == 8) {
+ if(sscanf(subnetstr, "%hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx#%d",
+ &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &x[6], &x[7], &weight) >= 8) {
subnet->type = SUBNET_IPV6;
subnet->net.ipv6.prefixlength = 128;
subnet->type = SUBNET_IPV6;
subnet->net.ipv6.prefixlength = 128;
+ subnet->weight = weight;
for(i = 0; i < 8; i++)
subnet->net.ipv6.address.x[i] = htons(x[i]);
for(i = 0; i < 8; i++)
subnet->net.ipv6.address.x[i] = htons(x[i]);
- if(sscanf(subnetstr, "%hx:%hx:%hx:%hx:%hx:%hx",
- &x[0], &x[1], &x[2], &x[3], &x[4], &x[5]) == 6) {
+ if(sscanf(subnetstr, "%hx:%hx:%hx:%hx:%hx:%hx#%d",
+ &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &weight) >= 6) {
subnet->type = SUBNET_MAC;
subnet->type = SUBNET_MAC;
+ subnet->weight = weight;
for(i = 0; i < 6; i++)
subnet->net.mac.address.x[i] = x[i];
for(i = 0; i < 6; i++)
subnet->net.mac.address.x[i] = x[i];
switch (subnet->type) {
case SUBNET_MAC:
switch (subnet->type) {
case SUBNET_MAC:
- snprintf(netstr, len, "%hx:%hx:%hx:%hx:%hx:%hx",
+ snprintf(netstr, len, "%hx:%hx:%hx:%hx:%hx:%hx#%d",
subnet->net.mac.address.x[0],
subnet->net.mac.address.x[1],
subnet->net.mac.address.x[2],
subnet->net.mac.address.x[3],
subnet->net.mac.address.x[0],
subnet->net.mac.address.x[1],
subnet->net.mac.address.x[2],
subnet->net.mac.address.x[3],
- subnet->net.mac.address.x[4], subnet->net.mac.address.x[5]);
+ subnet->net.mac.address.x[4],
+ subnet->net.mac.address.x[5],
+ subnet->weight);
- snprintf(netstr, len, "%hu.%hu.%hu.%hu/%d",
+ snprintf(netstr, len, "%hu.%hu.%hu.%hu/%d#%d",
subnet->net.ipv4.address.x[0],
subnet->net.ipv4.address.x[1],
subnet->net.ipv4.address.x[2],
subnet->net.ipv4.address.x[0],
subnet->net.ipv4.address.x[1],
subnet->net.ipv4.address.x[2],
- subnet->net.ipv4.address.x[3], subnet->net.ipv4.prefixlength);
+ subnet->net.ipv4.address.x[3],
+ subnet->net.ipv4.prefixlength,
+ subnet->weight);
- snprintf(netstr, len, "%hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx/%d",
+ snprintf(netstr, len, "%hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx/%d#%d",
ntohs(subnet->net.ipv6.address.x[0]),
ntohs(subnet->net.ipv6.address.x[1]),
ntohs(subnet->net.ipv6.address.x[2]),
ntohs(subnet->net.ipv6.address.x[0]),
ntohs(subnet->net.ipv6.address.x[1]),
ntohs(subnet->net.ipv6.address.x[2]),
ntohs(subnet->net.ipv6.address.x[5]),
ntohs(subnet->net.ipv6.address.x[6]),
ntohs(subnet->net.ipv6.address.x[7]),
ntohs(subnet->net.ipv6.address.x[5]),
ntohs(subnet->net.ipv6.address.x[6]),
ntohs(subnet->net.ipv6.address.x[7]),
- subnet->net.ipv6.prefixlength);
+ subnet->net.ipv6.prefixlength,
+ subnet->weight);
subnet_t *lookup_subnet_ipv4(const ipv4_t *address)
{
subnet_t *lookup_subnet_ipv4(const ipv4_t *address)
{
- subnet_t *p, subnet = {0};
+ subnet_t *p, *r = NULL, subnet = {0};
+ avl_node_t *n;
+ int i;
+ // Check if this address is cached
+
+ for(i = 0; i < 2; i++) {
+ if(!cache_ipv4_valid[i])
+ continue;
+ if(!memcmp(address, &cache_ipv4_address[i], sizeof *address))
+ return cache_ipv4_subnet[i];
+ }
+
+ // Search all subnets for a matching one
+
subnet.type = SUBNET_IPV4;
subnet.net.ipv4.address = *address;
subnet.net.ipv4.prefixlength = 32;
subnet.owner = NULL;
subnet.type = SUBNET_IPV4;
subnet.net.ipv4.address = *address;
subnet.net.ipv4.prefixlength = 32;
subnet.owner = NULL;
- do {
- /* Go find subnet */
-
- p = avl_search_closest_smaller(subnet_tree, &subnet);
-
- /* Check if the found subnet REALLY matches */
-
- if(p) {
- if(p->type != SUBNET_IPV4) {
- p = NULL;
- break;
- }
+ for(n = subnet_tree->head; n; n = n->next) {
+ p = n->data;
+
+ if(!p || p->type != subnet.type)
+ continue;
- if(!maskcmp(address, &p->net.ipv4.address, p->net.ipv4.prefixlength))
+ if(!maskcmp(address, &p->net.ipv4.address, p->net.ipv4.prefixlength)) {
+ r = p;
+ if(p->owner->status.reachable)
- else {
- /* Otherwise, see if there is a bigger enclosing subnet */
-
- subnet.net.ipv4.prefixlength = p->net.ipv4.prefixlength - 1;
- if(subnet.net.ipv4.prefixlength < 0 || subnet.net.ipv4.prefixlength > 32)
- return NULL;
- maskcpy(&subnet.net.ipv4.address, &p->net.ipv4.address, subnet.net.ipv4.prefixlength, sizeof(ipv4_t));
- }
+ // Cache the result
+
+ cache_ipv4_slot = !cache_ipv4_slot;
+ memcpy(&cache_ipv4_address[cache_ipv4_slot], address, sizeof *address);
+ cache_ipv4_subnet[cache_ipv4_slot] = r;
+ cache_ipv4_valid[cache_ipv4_slot] = true;
+
+ return r;
}
subnet_t *lookup_subnet_ipv6(const ipv6_t *address)
{
}
subnet_t *lookup_subnet_ipv6(const ipv6_t *address)
{
- subnet_t *p, subnet = {0};
+ subnet_t *p, *r = NULL, subnet = {0};
+ avl_node_t *n;
+ int i;
+ // Check if this address is cached
+
+ for(i = 0; i < 2; i++) {
+ if(!cache_ipv6_valid[i])
+ continue;
+ if(!memcmp(address, &cache_ipv6_address[i], sizeof *address))
+ return cache_ipv6_subnet[i];
+ }
+
+ // Search all subnets for a matching one
+
subnet.type = SUBNET_IPV6;
subnet.net.ipv6.address = *address;
subnet.net.ipv6.prefixlength = 128;
subnet.owner = NULL;
subnet.type = SUBNET_IPV6;
subnet.net.ipv6.address = *address;
subnet.net.ipv6.prefixlength = 128;
subnet.owner = NULL;
- do {
- /* Go find subnet */
-
- p = avl_search_closest_smaller(subnet_tree, &subnet);
-
- /* Check if the found subnet REALLY matches */
-
- if(p) {
- if(p->type != SUBNET_IPV6)
- return NULL;
+ for(n = subnet_tree->head; n; n = n->next) {
+ p = n->data;
+
+ if(!p || p->type != subnet.type)
+ continue;
- if(!maskcmp(address, &p->net.ipv6.address, p->net.ipv6.prefixlength))
+ if(!maskcmp(address, &p->net.ipv6.address, p->net.ipv6.prefixlength)) {
+ r = p;
+ if(p->owner->status.reachable)
- else {
- /* Otherwise, see if there is a bigger enclosing subnet */
-
- subnet.net.ipv6.prefixlength = p->net.ipv6.prefixlength - 1;
- if(subnet.net.ipv6.prefixlength < 0 || subnet.net.ipv6.prefixlength > 128)
- return NULL;
- maskcpy(&subnet.net.ipv6.address, &p->net.ipv6.address, subnet.net.ipv6.prefixlength, sizeof(ipv6_t));
- }
+ // Cache the result
+
+ cache_ipv6_slot = !cache_ipv6_slot;
+ memcpy(&cache_ipv6_address[cache_ipv6_slot], address, sizeof *address);
+ cache_ipv6_subnet[cache_ipv6_slot] = r;
+ cache_ipv6_valid[cache_ipv6_slot] = true;
+
+ return r;
}
void subnet_update(node_t *owner, subnet_t *subnet, bool up) {
}
void subnet_update(node_t *owner, subnet_t *subnet, bool up) {
subnet_type_t type; /* subnet type (IPv4? IPv6? MAC? something even weirder?) */
time_t expires; /* expiry time */
subnet_type_t type; /* subnet type (IPv4? IPv6? MAC? something even weirder?) */
time_t expires; /* expiry time */
+ int weight; /* weight (higher value is higher priority) */
/* And now for the actual subnet: */
/* And now for the actual subnet: */
extern subnet_t *lookup_subnet_ipv4(const ipv4_t *);
extern subnet_t *lookup_subnet_ipv6(const ipv6_t *);
extern void dump_subnets(void);
extern subnet_t *lookup_subnet_ipv4(const ipv4_t *);
extern subnet_t *lookup_subnet_ipv6(const ipv6_t *);
extern void dump_subnets(void);
+extern void subnet_cache_flush(void);
#endif /* __TINC_SUBNET_H__ */
#endif /* __TINC_SUBNET_H__ */