Improve recently seen address cache
[tinc] / src / address_cache.c
1 /*
2     address_cache.c -- Manage cache of recently seen addresses
3     Copyright (C) 2018 Guus Sliepen <guus@tinc-vpn.org>
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License along
16     with this program; if not, write to the Free Software Foundation, Inc.,
17     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "system.h"
21
22 #include "address_cache.h"
23 #include "conf.h"
24 #include "names.h"
25 #include "netutl.h"
26 #include "xalloc.h"
27
28 static const unsigned int NOT_CACHED = UINT_MAX;
29
30 // Find edges pointing to this node, and use them to build a list of unique, known addresses.
31 static struct addrinfo *get_known_addresses(node_t *n) {
32         struct addrinfo *ai = NULL;
33         struct addrinfo *oai = NULL;
34
35         for splay_each(edge_t, e, &n->edge_tree) {
36                 if(!e->reverse) {
37                         continue;
38                 }
39
40                 bool found = false;
41
42                 for(struct addrinfo *aip = ai; aip; aip = aip->ai_next) {
43                         if(!sockaddrcmp(&e->reverse->address, (sockaddr_t *)aip->ai_addr)) {
44                                 found = true;
45                                 break;
46                         }
47                 }
48
49                 if(found) {
50                         continue;
51                 }
52
53                 oai = ai;
54                 ai = xzalloc(sizeof(*ai));
55                 ai->ai_family = e->reverse->address.sa.sa_family;
56                 ai->ai_socktype = SOCK_STREAM;
57                 ai->ai_protocol = IPPROTO_TCP;
58                 ai->ai_addrlen = SALEN(e->reverse->address.sa);
59                 ai->ai_addr = xmalloc(ai->ai_addrlen);
60                 memcpy(ai->ai_addr, &e->reverse->address, ai->ai_addrlen);
61                 ai->ai_next = oai;
62         }
63
64         return ai;
65 }
66
67 static void free_known_addresses(struct addrinfo *ai) {
68         for(struct addrinfo *aip = ai, *next; aip; aip = next) {
69                 next = aip->ai_next;
70                 free(aip->ai_addr);
71                 free(aip);
72         }
73 }
74
75 static unsigned int find_cached(address_cache_t *cache, const sockaddr_t *sa) {
76         for(unsigned int i = 0; i < cache->data.used; i++)
77                 if(!sockaddrcmp(&cache->data.address[i], sa)) {
78                         return i;
79                 }
80
81         return NOT_CACHED;
82 }
83
84 void add_recent_address(address_cache_t *cache, const sockaddr_t *sa) {
85         // Check if it's already cached
86         unsigned int pos = find_cached(cache, sa);
87
88         // It's in the first spot, so nothing to do
89         if(pos == 0) {
90                 return;
91         }
92
93         logger(DEBUG_CONNECTIONS, LOG_DEBUG, "Caching recent address for %s", cache->node->name);
94
95         // Shift everything, move/add the address to the first slot
96         if(pos == NOT_CACHED) {
97                 if(cache->data.used < MAX_CACHED_ADDRESSES) {
98                         cache->data.used++;
99                 }
100
101                 pos = cache->data.used - 1;
102         }
103
104         memmove(&cache->data.address[1], &cache->data.address[0], pos * sizeof(cache->data.address[0]));
105
106         cache->data.address[0] = *sa;
107
108         // Write the cache
109         char fname[PATH_MAX];
110         snprintf(fname, sizeof(fname), "%s" SLASH "cache" SLASH "%s", confbase, cache->node->name);
111         FILE *fp = fopen(fname, "wb");
112
113         if(fp) {
114                 fwrite(&cache->data, sizeof(cache->data), 1, fp);
115                 fclose(fp);
116         }
117 }
118
119 const sockaddr_t *get_recent_address(address_cache_t *cache) {
120         // Check if there is an address in our cache of recently seen addresses
121         if(cache->tried < cache->data.used) {
122                 return &cache->data.address[cache->tried++];
123         }
124
125         // Next, check any recently seen addresses not in our cache
126         while(cache->tried == cache->data.used) {
127                 if(!cache->ai) {
128                         cache->aip = cache->ai = get_known_addresses(cache->node);
129                 }
130
131                 if(cache->ai) {
132                         if(cache->aip) {
133                                 sockaddr_t *sa = (sockaddr_t *)cache->aip->ai_addr;
134                                 cache->aip = cache->aip->ai_next;
135
136                                 if(find_cached(cache, sa) != NOT_CACHED) {
137                                         continue;
138                                 }
139
140                                 return sa;
141                         } else {
142                                 free_known_addresses(cache->ai);
143                                 cache->ai = NULL;
144                         }
145                 }
146
147                 cache->tried++;
148         }
149
150         // Otherwise, check if there are any known Address statements
151         if(!cache->config_tree) {
152                 cache->config_tree = create_configuration();
153                 read_host_config(cache->config_tree, cache->node->name, false);
154                 cache->cfg = lookup_config(cache->config_tree, "Address");
155         }
156
157         while(cache->cfg && !cache->aip) {
158                 char *address, *port;
159
160                 get_config_string(cache->cfg, &address);
161
162                 char *space = strchr(address, ' ');
163
164                 if(space) {
165                         port = xstrdup(space + 1);
166                         *space = 0;
167                 } else {
168                         if(!get_config_string(lookup_config(cache->config_tree, "Port"), &port)) {
169                                 port = xstrdup("655");
170                         }
171                 }
172
173                 if(cache->ai) {
174                         free_known_addresses(cache->ai);
175                 }
176
177                 cache->aip = cache->ai = str2addrinfo(address, port, SOCK_STREAM);
178
179                 if(cache->ai) {
180                         struct addrinfo *ai = NULL;
181
182                         for(; cache->aip; cache->aip = cache->aip->ai_next) {
183                                 struct addrinfo *oai = ai;
184
185                                 ai = xzalloc(sizeof(*ai));
186                                 ai->ai_family = cache->aip->ai_family;
187                                 ai->ai_socktype = cache->aip->ai_socktype;
188                                 ai->ai_protocol = cache->aip->ai_protocol;
189                                 ai->ai_addrlen = cache->aip->ai_addrlen;
190                                 ai->ai_addr = xmalloc(ai->ai_addrlen);
191                                 memcpy(ai->ai_addr, cache->aip->ai_addr, ai->ai_addrlen);
192                                 ai->ai_next = oai;
193                         }
194
195                         freeaddrinfo(cache->ai);
196                         cache->aip = cache->ai = ai;
197                 }
198
199                 free(address);
200                 free(port);
201
202                 cache->cfg = lookup_config_next(cache->config_tree, cache->cfg);
203         }
204
205         if(cache->ai) {
206                 if(cache->aip) {
207                         sockaddr_t *sa = (sockaddr_t *)cache->aip->ai_addr;
208
209                         cache->aip = cache->aip->ai_next;
210                         return sa;
211                 } else {
212                         free_known_addresses(cache->ai);
213                         cache->ai = NULL;
214                 }
215         }
216
217         // We're all out of addresses.
218         exit_configuration(cache->config_tree);
219         cache->config_tree = NULL;
220
221         return NULL;
222 }
223
224 address_cache_t *open_address_cache(node_t *node) {
225         address_cache_t *cache = xmalloc(sizeof(*cache));
226         cache->node = node;
227
228         // Try to open an existing address cache
229         char fname[PATH_MAX];
230         snprintf(fname, sizeof(fname), "%s" SLASH "cache" SLASH "%s", confbase, node->name);
231         FILE *fp = fopen(fname, "rb");
232
233         if(!fp || fread(&cache->data, sizeof(cache->data), 1, fp) != 1 || cache->data.version != ADDRESS_CACHE_VERSION) {
234                 memset(&cache->data, 0, sizeof(cache->data));
235         }
236
237         if(fp) {
238                 fclose(fp);
239         }
240
241         // Ensure we have a valid state
242         cache->config_tree = NULL;
243         cache->cfg = NULL;
244         cache->ai = NULL;
245         cache->aip = NULL;
246         cache->tried = 0;
247         cache->data.version = ADDRESS_CACHE_VERSION;
248
249         if(cache->data.used > MAX_CACHED_ADDRESSES) {
250                 cache->data.used = 0;
251         }
252
253         return cache;
254 }
255
256 void reset_address_cache(address_cache_t *cache) {
257         if(cache->config_tree) {
258                 exit_configuration(cache->config_tree);
259                 cache->config_tree = NULL;
260         }
261
262         if(cache->ai) {
263                 free_known_addresses(cache->ai);
264         }
265
266         cache->config_tree = NULL;
267         cache->cfg = NULL;
268         cache->ai = NULL;
269         cache->aip = NULL;
270         cache->tried = 0;
271 }
272
273 void close_address_cache(address_cache_t *cache) {
274         if(cache->config_tree) {
275                 exit_configuration(cache->config_tree);
276                 cache->config_tree = NULL;
277         }
278
279         if(cache->ai) {
280                 free_known_addresses(cache->ai);
281         }
282
283         free(cache);
284 }