have sanity checks for DHT path construction
[oweals/gnunet.git] / src / dht / gnunet-service-dht_routing.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2011 GNUnet e.V.
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 3, or (at your
8      option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18      Boston, MA 02110-1301, USA.
19 */
20
21 /**
22  * @file dht/gnunet-service-dht_routing.c
23  * @brief GNUnet DHT tracking of requests for routing replies
24  * @author Christian Grothoff
25  */
26 #include "platform.h"
27 #include "gnunet-service-dht_neighbours.h"
28 #include "gnunet-service-dht_routing.h"
29 #include "gnunet-service-dht.h"
30
31
32 /**
33  * Number of requests we track at most (for routing replies).
34  */
35 #define DHT_MAX_RECENT (1024 * 16)
36
37
38 /**
39  * Information we keep about all recent GET requests
40  * so that we can route replies.
41  */
42 struct RecentRequest
43 {
44
45   /**
46    * The peer this request was received from.
47    */
48   struct GNUNET_PeerIdentity peer;
49
50   /**
51    * Key of this request.
52    */
53   struct GNUNET_HashCode key;
54
55   /**
56    * Position of this node in the min heap.
57    */
58   struct GNUNET_CONTAINER_HeapNode *heap_node;
59
60   /**
61    * Bloomfilter for replies to drop.
62    */
63   struct GNUNET_CONTAINER_BloomFilter *reply_bf;
64
65   /**
66    * Type of the requested block.
67    */
68   enum GNUNET_BLOCK_Type type;
69
70   /**
71    * extended query (see gnunet_block_lib.h).  Allocated at the
72    * end of this struct.
73    */
74   const void *xquery;
75
76   /**
77    * Number of bytes in xquery.
78    */
79   size_t xquery_size;
80
81   /**
82    * Mutator value for the reply_bf, see gnunet_block_lib.h
83    */
84   uint32_t reply_bf_mutator;
85
86   /**
87    * Request options.
88    */
89   enum GNUNET_DHT_RouteOption options;
90
91 };
92
93
94 /**
95  * Recent requests by time inserted.
96  */
97 static struct GNUNET_CONTAINER_Heap *recent_heap;
98
99 /**
100  * Recently seen requests by key.
101  */
102 static struct GNUNET_CONTAINER_MultiHashMap *recent_map;
103
104
105 /**
106  * Closure for the 'process' function.
107  */
108 struct ProcessContext
109 {
110   /**
111    * Path of the original PUT
112    */
113   const struct GNUNET_PeerIdentity *put_path;
114
115   /**
116    * Path of the reply.
117    */
118   const struct GNUNET_PeerIdentity *get_path;
119
120   /**
121    * Payload of the reply.
122    */
123   const void *data;
124
125   /**
126    * Expiration time of the result.
127    */
128   struct GNUNET_TIME_Absolute expiration_time;
129
130   /**
131    * Number of entries in @e put_path.
132    */
133   unsigned int put_path_length;
134
135   /**
136    * Number of entries in @e get_path.
137    */
138   unsigned int get_path_length;
139
140   /**
141    * Number of bytes in @e data.
142    */
143   size_t data_size;
144
145   /**
146    * Type of the reply.
147    */
148   enum GNUNET_BLOCK_Type type;
149
150 };
151
152
153 /**
154  * Forward the result to the given peer if it matches the request.
155  *
156  * @param cls the `struct ProcessContext` with the result
157  * @param key the query
158  * @param value the `struct RecentRequest` with the request
159  * @return #GNUNET_OK (continue to iterate),
160  *         #GNUNET_SYSERR if the result is malformed or type unsupported
161  */
162 static int
163 process (void *cls,
164          const struct GNUNET_HashCode *key,
165          void *value)
166 {
167   struct ProcessContext *pc = cls;
168   struct RecentRequest *rr = value;
169   enum GNUNET_BLOCK_EvaluationResult eval;
170   unsigned int gpl;
171   unsigned int ppl;
172   struct GNUNET_HashCode hc;
173   const struct GNUNET_HashCode *eval_key;
174
175   if ( (rr->type != GNUNET_BLOCK_TYPE_ANY) &&
176        (rr->type != pc->type) )
177     return GNUNET_OK;           /* type missmatch */
178
179   if (0 != (rr->options & GNUNET_DHT_RO_RECORD_ROUTE))
180   {
181     gpl = pc->get_path_length;
182     ppl = pc->put_path_length;
183   }
184   else
185   {
186     gpl = 0;
187     ppl = 0;
188   }
189   if ( (0 != (rr->options & GNUNET_DHT_RO_FIND_PEER)) &&
190        (pc->type == GNUNET_BLOCK_TYPE_DHT_HELLO) )
191   {
192     /* key may not match HELLO, which is OK since
193      * the search is approximate.  Still, the evaluation
194      * would fail since the match is not exact.  So
195      * we fake it by changing the key to the actual PID ... */
196     GNUNET_BLOCK_get_key (GDS_block_context,
197                           GNUNET_BLOCK_TYPE_DHT_HELLO,
198                           pc->data,
199                           pc->data_size,
200                           &hc);
201     eval_key = &hc;
202   }
203   else
204   {
205     eval_key = key;
206   }
207   eval
208     = GNUNET_BLOCK_evaluate (GDS_block_context,
209                              pc->type,
210                              GNUNET_BLOCK_EO_NONE,
211                              eval_key,
212                              &rr->reply_bf,
213                              rr->reply_bf_mutator,
214                              rr->xquery,
215                              rr->xquery_size,
216                              pc->data,
217                              pc->data_size);
218   switch (eval)
219   {
220   case GNUNET_BLOCK_EVALUATION_OK_MORE:
221   case GNUNET_BLOCK_EVALUATION_OK_LAST:
222     GNUNET_STATISTICS_update (GDS_stats,
223                               gettext_noop
224                               ("# Good REPLIES matched against routing table"),
225                               1, GNUNET_NO);
226     GDS_NEIGHBOURS_handle_reply (&rr->peer,
227                                  pc->type,
228                                  pc->expiration_time,
229                                  key,
230                                  ppl, pc->put_path,
231                                  gpl, pc->get_path,
232                                  pc->data,
233                                  pc->data_size);
234     break;
235   case GNUNET_BLOCK_EVALUATION_OK_DUPLICATE:
236     GNUNET_STATISTICS_update (GDS_stats,
237                               gettext_noop
238                               ("# Duplicate REPLIES matched against routing table"),
239                               1, GNUNET_NO);
240     return GNUNET_OK;
241   case GNUNET_BLOCK_EVALUATION_RESULT_INVALID:
242     GNUNET_STATISTICS_update (GDS_stats,
243                               gettext_noop
244                               ("# Invalid REPLIES matched against routing table"),
245                               1, GNUNET_NO);
246     return GNUNET_SYSERR;
247   case GNUNET_BLOCK_EVALUATION_RESULT_IRRELEVANT:
248     GNUNET_STATISTICS_update (GDS_stats,
249                               gettext_noop
250                               ("# Irrelevant REPLIES matched against routing table"),
251                               1, GNUNET_NO);
252     return GNUNET_OK;
253   case GNUNET_BLOCK_EVALUATION_REQUEST_VALID:
254     GNUNET_break (0);
255     return GNUNET_OK;
256   case GNUNET_BLOCK_EVALUATION_REQUEST_INVALID:
257     GNUNET_break (0);
258     return GNUNET_OK;
259   case GNUNET_BLOCK_EVALUATION_TYPE_NOT_SUPPORTED:
260     GNUNET_STATISTICS_update (GDS_stats,
261                               gettext_noop
262                               ("# Unsupported REPLIES matched against routing table"),
263                               1, GNUNET_NO);
264     return GNUNET_SYSERR;
265   default:
266     GNUNET_break (0);
267     return GNUNET_SYSERR;
268   }
269   return GNUNET_OK;
270 }
271
272
273 /**
274  * Handle a reply (route to origin).  Only forwards the reply back to
275  * other peers waiting for it.  Does not do local caching or
276  * forwarding to local clients.  Essentially calls
277  * GDS_NEIGHBOURS_handle_reply for all peers that sent us a matching
278  * request recently.
279  *
280  * @param type type of the block
281  * @param expiration_time when does the content expire
282  * @param key key for the content
283  * @param put_path_length number of entries in @a put_path
284  * @param put_path peers the original PUT traversed (if tracked)
285  * @param get_path_length number of entries in @a get_path
286  * @param get_path peers this reply has traversed so far (if tracked)
287  * @param data payload of the reply
288  * @param data_size number of bytes in data
289  */
290 void
291 GDS_ROUTING_process (void *cls,
292                      enum GNUNET_BLOCK_Type type,
293                      struct GNUNET_TIME_Absolute expiration_time,
294                      const struct GNUNET_HashCode *key,
295                      unsigned int put_path_length,
296                      const struct GNUNET_PeerIdentity *put_path,
297                      unsigned int get_path_length,
298                      const struct GNUNET_PeerIdentity *get_path,
299                      const void *data,
300                      size_t data_size)
301 {
302   struct ProcessContext pc;
303
304   pc.type = type;
305   pc.expiration_time = expiration_time;
306   pc.put_path_length = put_path_length;
307   pc.put_path = put_path;
308   pc.get_path_length = get_path_length;
309   pc.get_path = get_path;
310   pc.data = data;
311   pc.data_size = data_size;
312   if (NULL == data)
313   {
314     /* Some apps might have an 'empty' reply as a valid reply; however,
315        'process' will call GNUNET_BLOCK_evaluate' which treats a 'NULL'
316        reply as request-validation (but we need response-validation).
317        So we set 'data' to a 0-byte non-NULL value just to be sure */
318     GNUNET_break (0 == data_size);
319     pc.data_size = 0;
320     pc.data = ""; /* something not null */
321   }
322   GNUNET_CONTAINER_multihashmap_get_multiple (recent_map,
323                                               key,
324                                               &process,
325                                               &pc);
326 }
327
328
329 /**
330  * Remove the oldest entry from the DHT routing table.  Must only
331  * be called if it is known that there is at least one entry
332  * in the heap and hashmap.
333  */
334 static void
335 expire_oldest_entry ()
336 {
337   struct RecentRequest *recent_req;
338
339   GNUNET_STATISTICS_update (GDS_stats,
340                             gettext_noop
341                             ("# Entries removed from routing table"), 1,
342                             GNUNET_NO);
343   recent_req = GNUNET_CONTAINER_heap_peek (recent_heap);
344   GNUNET_assert (recent_req != NULL);
345   GNUNET_CONTAINER_heap_remove_node (recent_req->heap_node);
346   GNUNET_CONTAINER_bloomfilter_free (recent_req->reply_bf);
347   GNUNET_assert (GNUNET_YES ==
348                  GNUNET_CONTAINER_multihashmap_remove (recent_map,
349                                                        &recent_req->key,
350                                                        recent_req));
351   GNUNET_free (recent_req);
352 }
353
354
355 /**
356  * Try to combine multiple recent requests for the same value
357  * (if they come from the same peer).
358  *
359  * @param cls the new 'struct RecentRequest' (to discard upon successful combination)
360  * @param key the query
361  * @param value the existing 'struct RecentRequest' (to update upon successful combination)
362  * @return #GNUNET_OK (continue to iterate),
363  *         #GNUNET_SYSERR if the request was successfully combined
364  */
365 static int
366 try_combine_recent (void *cls,
367                     const struct GNUNET_HashCode *key,
368                     void *value)
369 {
370   struct RecentRequest *in = cls;
371   struct RecentRequest *rr = value;
372
373   if ( (0 != memcmp (&in->peer,
374                      &rr->peer,
375                      sizeof (struct GNUNET_PeerIdentity))) ||
376        (in->type != rr->type) ||
377        (in->xquery_size != rr->xquery_size) ||
378        (0 != memcmp (in->xquery,
379                      rr->xquery,
380                      in->xquery_size)) )
381     return GNUNET_OK;
382   if (in->reply_bf_mutator != rr->reply_bf_mutator)
383   {
384     rr->reply_bf_mutator = in->reply_bf_mutator;
385     GNUNET_CONTAINER_bloomfilter_free (rr->reply_bf);
386     rr->reply_bf = in->reply_bf;
387   }
388   else
389   {
390     GNUNET_CONTAINER_bloomfilter_or2 (rr->reply_bf,
391                                       in->reply_bf);
392     GNUNET_CONTAINER_bloomfilter_free (in->reply_bf);
393   }
394   GNUNET_free (in);
395   return GNUNET_SYSERR;
396 }
397
398
399 /**
400  * Add a new entry to our routing table.
401  *
402  * @param sender peer that originated the request
403  * @param type type of the block
404  * @param options options for processing
405  * @param key key for the content
406  * @param xquery extended query
407  * @param xquery_size number of bytes in @a xquery
408  * @param reply_bf bloomfilter to filter duplicates
409  * @param reply_bf_mutator mutator for @a reply_bf
410  */
411 void
412 GDS_ROUTING_add (const struct GNUNET_PeerIdentity *sender,
413                  enum GNUNET_BLOCK_Type type,
414                  enum GNUNET_DHT_RouteOption options,
415                  const struct GNUNET_HashCode *key,
416                  const void *xquery,
417                  size_t xquery_size,
418                  const struct GNUNET_CONTAINER_BloomFilter *reply_bf,
419                  uint32_t reply_bf_mutator)
420 {
421   struct RecentRequest *recent_req;
422
423   while (GNUNET_CONTAINER_heap_get_size (recent_heap) >= DHT_MAX_RECENT)
424     expire_oldest_entry ();
425   GNUNET_STATISTICS_update (GDS_stats,
426                             gettext_noop ("# Entries added to routing table"),
427                             1, GNUNET_NO);
428   recent_req = GNUNET_malloc (sizeof (struct RecentRequest) + xquery_size);
429   recent_req->peer = *sender;
430   recent_req->key = *key;
431   recent_req->reply_bf = GNUNET_CONTAINER_bloomfilter_copy (reply_bf);
432   recent_req->type = type;
433   recent_req->options = options;
434   recent_req->xquery = &recent_req[1];
435   GNUNET_memcpy (&recent_req[1], xquery, xquery_size);
436   recent_req->xquery_size = xquery_size;
437   recent_req->reply_bf_mutator = reply_bf_mutator;
438   if (GNUNET_SYSERR ==
439       GNUNET_CONTAINER_multihashmap_get_multiple (recent_map,
440                                                   key,
441                                                   &try_combine_recent,
442                                                   recent_req))
443   {
444     GNUNET_STATISTICS_update (GDS_stats,
445                               gettext_noop
446                               ("# DHT requests combined"),
447                               1, GNUNET_NO);
448     return;
449   }
450   recent_req->heap_node =
451       GNUNET_CONTAINER_heap_insert (recent_heap, recent_req,
452                                     GNUNET_TIME_absolute_get ().abs_value_us);
453   GNUNET_CONTAINER_multihashmap_put (recent_map, key, recent_req,
454                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
455
456
457 }
458
459
460 /**
461  * Initialize routing subsystem.
462  */
463 void
464 GDS_ROUTING_init ()
465 {
466   recent_heap = GNUNET_CONTAINER_heap_create (GNUNET_CONTAINER_HEAP_ORDER_MIN);
467   recent_map = GNUNET_CONTAINER_multihashmap_create (DHT_MAX_RECENT * 4 / 3, GNUNET_NO);
468 }
469
470
471 /**
472  * Shutdown routing subsystem.
473  */
474 void
475 GDS_ROUTING_done ()
476 {
477   while (GNUNET_CONTAINER_heap_get_size (recent_heap) > 0)
478     expire_oldest_entry ();
479   GNUNET_assert (0 == GNUNET_CONTAINER_heap_get_size (recent_heap));
480   GNUNET_CONTAINER_heap_destroy (recent_heap);
481   recent_heap = NULL;
482   GNUNET_assert (0 == GNUNET_CONTAINER_multihashmap_size (recent_map));
483   GNUNET_CONTAINER_multihashmap_destroy (recent_map);
484   recent_map = NULL;
485 }
486
487 /* end of gnunet-service-dht_routing.c */