Merge pull request #78 from ffontaine/master
[olsrd.git] / src / routing_table.c
1 /*
2  * The olsr.org Optimized Link-State Routing daemon (olsrd)
3  *
4  * (c) by the OLSR project
5  *
6  * See our Git repository to find out who worked on this file
7  * and thus is a copyright holder on it.
8  *
9  * All rights reserved.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  *
15  * * Redistributions of source code must retain the above copyright
16  *   notice, this list of conditions and the following disclaimer.
17  * * Redistributions in binary form must reproduce the above copyright
18  *   notice, this list of conditions and the following disclaimer in
19  *   the documentation and/or other materials provided with the
20  *   distribution.
21  * * Neither the name of olsr.org, olsrd nor the names of its
22  *   contributors may be used to endorse or promote products derived
23  *   from this software without specific prior written permission.
24  *
25  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
28  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
29  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
30  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
31  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
32  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
33  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
35  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36  * POSSIBILITY OF SUCH DAMAGE.
37  *
38  * Visit http://www.olsr.org for more information.
39  *
40  * If you find this software useful feel free to make a donation
41  * to the project. For more information see the website or contact
42  * the copyright holders.
43  *
44  */
45
46 #include "routing_table.h"
47 #include "ipcalc.h"
48 #include "defs.h"
49 #include "two_hop_neighbor_table.h"
50 #include "tc_set.h"
51 #include "mid_set.h"
52 #include "neighbor_table.h"
53 #include "olsr.h"
54 #include "link_set.h"
55 #include "common/avl.h"
56 #include "olsr_spf.h"
57 #include "net_olsr.h"
58
59 #include <assert.h>
60
61 /* Cookies */
62 struct olsr_cookie_info *rt_mem_cookie = NULL;
63 struct olsr_cookie_info *rtp_mem_cookie = NULL;
64
65 /*
66  * Sven-Ola: if the current internet gateway is switched, the
67  * NAT connection info is lost for any active TCP/UDP session.
68  * For this reason, we do not want to switch if the advantage
69  * is only minimal (cost of loosing all NATs is too high).
70  * The following rt_path keeps track of the current inet gw.
71  */
72 static struct rt_path *current_inetgw = NULL;
73
74 /* Root of our RIB */
75 struct avl_tree routingtree;
76
77 /*
78  * Keep a version number for detecting outdated elements
79  * in the per rt_entry rt_path subtree.
80  */
81 unsigned int routingtree_version;
82
83 /**
84  * Bump the version number of the routing tree.
85  *
86  * After route-insertion compare the version number of the routes
87  * against the version number of the table.
88  * This is a lightweight detection if a node or prefix went away,
89  * rather than brute force old vs. new rt_entry comparision.
90  */
91 unsigned int
92 olsr_bump_routingtree_version(void)
93 {
94   return routingtree_version++;
95 }
96
97 /**
98  * avl_comp_ipv4_prefix
99  *
100  * compare two ipv4 prefixes.
101  * first compare the prefixes, then
102  *  then compare the prefix lengths.
103  *
104  * return 0 if there is an exact match and
105  * -1 / +1 depending on being smaller or bigger.
106  */
107 int
108 avl_comp_ipv4_prefix(const void *prefix1, const void *prefix2)
109 {
110   const struct olsr_ip_prefix *pfx1 = prefix1;
111   const struct olsr_ip_prefix *pfx2 = prefix2;
112   const uint32_t addr1 = ntohl(pfx1->prefix.v4.s_addr);
113   const uint32_t addr2 = ntohl(pfx2->prefix.v4.s_addr);
114
115   /* prefix */
116   if (addr1 < addr2) {
117     return -1;
118   }
119   if (addr1 > addr2) {
120     return +1;
121   }
122
123   /* prefix length */
124   if (pfx1->prefix_len < pfx2->prefix_len) {
125     return -1;
126   }
127   if (pfx1->prefix_len > pfx2->prefix_len) {
128     return +1;
129   }
130
131   return 0;
132 }
133
134 /**
135  * avl_comp_ipv6_prefix
136  *
137  * compare two ipv6 prefixes.
138  * first compare the prefixes, then
139  *  then compare the prefix lengths.
140  *
141  * return 0 if there is an exact match and
142  * -1 / +1 depending on being smaller or bigger.
143  */
144 int
145 avl_comp_ipv6_prefix(const void *prefix1, const void *prefix2)
146 {
147   int res;
148   const struct olsr_ip_prefix *pfx1 = prefix1;
149   const struct olsr_ip_prefix *pfx2 = prefix2;
150
151   /* prefix */
152   res = memcmp(&pfx1->prefix.v6, &pfx2->prefix.v6, 16);
153   if (res != 0) {
154     return res;
155   }
156   /* prefix length */
157   if (pfx1->prefix_len < pfx2->prefix_len) {
158     return -1;
159   }
160   if (pfx1->prefix_len > pfx2->prefix_len) {
161     return +1;
162   }
163
164   return 0;
165 }
166
167 /**
168  * Initialize the routingtree and kernel change queues.
169  */
170 void
171 olsr_init_routing_table(void)
172 {
173   OLSR_PRINTF(5, "RIB: init routing tree\n");
174
175   /* the routing tree */
176   avl_init(&routingtree, avl_comp_prefix_default);
177   routingtree_version = 0;
178
179   /*
180    * Get some cookies for memory stats and memory recycling.
181    */
182   rt_mem_cookie = olsr_alloc_cookie("rt_entry", OLSR_COOKIE_TYPE_MEMORY);
183   olsr_cookie_set_memory_size(rt_mem_cookie, sizeof(struct rt_entry));
184
185   rtp_mem_cookie = olsr_alloc_cookie("rt_path", OLSR_COOKIE_TYPE_MEMORY);
186   olsr_cookie_set_memory_size(rtp_mem_cookie, sizeof(struct rt_path));
187 }
188
189 /**
190  * Look up a maxplen entry (= /32 or /128) in the routing table.
191  *
192  * @param dst the address of the entry
193  *
194  * @return a pointer to a rt_entry struct
195  * representing the route entry.
196  */
197 struct rt_entry *
198 olsr_lookup_routing_table(const union olsr_ip_addr *dst)
199 {
200   struct avl_node *rt_tree_node;
201   struct olsr_ip_prefix prefix;
202
203   prefix.prefix = *dst;
204   prefix.prefix_len = olsr_cnf->maxplen;
205
206   rt_tree_node = avl_find(&routingtree, &prefix);
207
208   return rt_tree_node ? rt_tree2rt(rt_tree_node) : NULL;
209 }
210
211 /**
212  * Update gateway/interface/etx/hopcount and the version for a route path.
213  */
214 void
215 olsr_update_rt_path(struct rt_path *rtp, struct tc_entry *tc, struct link_entry *link)
216 {
217
218   rtp->rtp_version = routingtree_version;
219
220   /* gateway */
221   rtp->rtp_nexthop.gateway = link->neighbor_iface_addr;
222
223   /* interface */
224   rtp->rtp_nexthop.iif_index = link->inter->if_index;
225
226   /* metric/etx */
227   rtp->rtp_metric.hops = tc->hops;
228   rtp->rtp_metric.cost = tc->path_cost;
229 }
230
231 /**
232  * Alloc and key a new rt_entry.
233  */
234 static struct rt_entry *
235 olsr_alloc_rt_entry(struct olsr_ip_prefix *prefix)
236 {
237   struct rt_entry *rt = olsr_cookie_malloc(rt_mem_cookie);
238   if (!rt) {
239     return NULL;
240   }
241
242   memset(rt, 0, sizeof(*rt));
243
244   /* Mark this entry as fresh (see process_routes.c:512) */
245   rt->rt_nexthop.iif_index = -1;
246
247   /* set key and backpointer prior to tree insertion */
248   rt->rt_dst = *prefix;
249
250   rt->rt_tree_node.key = &rt->rt_dst;
251   avl_insert(&routingtree, &rt->rt_tree_node, AVL_DUP_NO);
252
253   /* init the originator subtree */
254   avl_init(&rt->rt_path_tree, avl_comp_default);
255
256   return rt;
257 }
258
259 /**
260  * Alloc and key a new rt_path.
261  */
262 static struct rt_path *
263 olsr_alloc_rt_path(struct tc_entry *tc, struct olsr_ip_prefix *prefix, uint8_t origin)
264 {
265   struct rt_path *rtp = olsr_cookie_malloc(rtp_mem_cookie);
266
267   if (!rtp) {
268     return NULL;
269   }
270
271   memset(rtp, 0, sizeof(*rtp));
272
273   rtp->rtp_dst = *prefix;
274
275   /* set key and backpointer prior to tree insertion */
276   rtp->rtp_prefix_tree_node.key = &rtp->rtp_dst;
277
278   /* insert to the tc prefix tree */
279   avl_insert(&tc->prefix_tree, &rtp->rtp_prefix_tree_node, AVL_DUP_NO);
280   olsr_lock_tc_entry(tc);
281
282   /* backlink to the owning tc entry */
283   rtp->rtp_tc = tc;
284
285   /* store the origin of the route */
286   rtp->rtp_origin = origin;
287
288   return rtp;
289 }
290
291 /**
292  * Create a route entry for a given rt_path and
293  * insert it into the global RIB tree.
294  */
295 void
296 olsr_insert_rt_path(struct rt_path *rtp, struct tc_entry *tc, struct link_entry *link)
297 {
298   struct rt_entry *rt;
299   struct avl_node *node;
300
301   /*
302    * no unreachable routes please.
303    */
304   if (tc->path_cost >= ROUTE_COST_BROKEN) {
305     return;
306   }
307
308   /*
309    * No bogus prefix lengths.
310    */
311   if (rtp->rtp_dst.prefix_len > olsr_cnf->maxplen) {
312     return;
313   }
314
315   /*
316    * first check if there is a route_entry for the prefix.
317    */
318   node = avl_find(&routingtree, &rtp->rtp_dst);
319
320   if (!node) {
321
322     /* no route entry yet */
323     rt = olsr_alloc_rt_entry(&rtp->rtp_dst);
324
325     if (!rt) {
326       return;
327     }
328
329   } else {
330     rt = rt_tree2rt(node);
331   }
332
333   /* Now insert the rt_path to the owning rt_entry tree */
334   rtp->rtp_originator = tc->addr;
335
336   /* set key and backpointer prior to tree insertion */
337   rtp->rtp_tree_node.key = &rtp->rtp_originator;
338
339   /* insert to the route entry originator tree */
340   avl_insert(&rt->rt_path_tree, &rtp->rtp_tree_node, AVL_DUP_NO);
341
342   /* backlink to the owning route entry */
343   rtp->rtp_rt = rt;
344
345   /* update the version field and relevant parameters */
346   olsr_update_rt_path(rtp, tc, link);
347 }
348
349 /**
350  * Unlink and free a rt_path.
351  */
352 void
353 olsr_delete_rt_path(struct rt_path *rtp)
354 {
355   struct rt_entry* rt;
356
357   /* remove from the originator tree */
358   if (rtp->rtp_rt) {
359     avl_delete(&rtp->rtp_rt->rt_path_tree, &rtp->rtp_tree_node);
360     rtp->rtp_rt = NULL;
361   }
362
363   /* remove from the tc prefix tree */
364   if (rtp->rtp_tc) {
365     avl_delete(&rtp->rtp_tc->prefix_tree, &rtp->rtp_prefix_tree_node);
366     olsr_unlock_tc_entry(rtp->rtp_tc);
367     rtp->rtp_tc = NULL;
368   }
369
370   /* no current inet gw if the rt_path is removed */
371   if (current_inetgw == rtp) {
372     current_inetgw = NULL;
373   }
374
375   /* remove reference from rt_best */
376   OLSR_FOR_ALL_RT_ENTRIES(rt)
377   if (rt->rt_best == rtp){
378     rt->rt_best = NULL;
379     OLSR_PRINTF(7, "Removed rt_best.\n");
380   }
381   OLSR_FOR_ALL_RT_ENTRIES_END(rt)
382
383   olsr_cookie_free(rtp_mem_cookie, rtp);
384 }
385
386 /**
387  * Check if there is an interface or gateway change.
388  */
389 bool
390 olsr_nh_change(const struct rt_nexthop *nh1, const struct rt_nexthop *nh2)
391 {
392   if (!ipequal(&nh1->gateway, &nh2->gateway) || (nh1->iif_index != nh2->iif_index)) {
393     return true;
394   }
395   return false;
396 }
397
398 /**
399  * Check if there is a hopcount change.
400  */
401 bool
402 olsr_hopcount_change(const struct rt_metric * met1, const struct rt_metric * met2)
403 {
404   return (met1->hops != met2->hops);
405 }
406
407 /**
408  * Depending if flat_metric is configured and the kernel fib operation
409  * return the hopcount metric of a route.
410  * For adds this is the metric of best route after olsr_rt_best() election,
411  * for deletes this is the metric of the route that got stored in the rt_entry,
412  * during route installation.
413  */
414 uint8_t
415 olsr_fib_metric(const struct rt_metric * met)
416 {
417   if (FIBM_CORRECT == olsr_cnf->fib_metric) {
418     return met->hops;
419   }
420   return olsr_cnf->fib_metric_default;
421 }
422
423 /**
424  * depending on the operation (add/chg/del) the nexthop
425  * field from the route entry or best route path shall be used.
426  */
427 const struct rt_nexthop *
428 olsr_get_nh(const struct rt_entry *rt)
429 {
430
431   if (rt->rt_best) {
432
433     /* this is a route add/chg - grab nexthop from the best route. */
434     return &rt->rt_best->rtp_nexthop;
435   }
436
437   /* this is a route deletion - all routes are gone. */
438   return &rt->rt_nexthop;
439 }
440
441 /**
442  * compare two route paths.
443  *
444  * returns TRUE if the first path is better
445  * than the second one, FALSE otherwise.
446  */
447 static bool
448 olsr_cmp_rtp(const struct rt_path *rtp1, const struct rt_path *rtp2, const struct rt_path *inetgw)
449 {
450   olsr_linkcost etx1 = rtp1->rtp_metric.cost;
451   olsr_linkcost etx2 = rtp2->rtp_metric.cost;
452   if (inetgw == rtp1)
453     etx1 *= olsr_cnf->lq_nat_thresh;
454   if (inetgw == rtp2)
455     etx2 *= olsr_cnf->lq_nat_thresh;
456
457   /* etx comes first */
458   if (etx1 < etx2) {
459     return true;
460   }
461   if (etx1 > etx2) {
462     return false;
463   }
464
465   /* hopcount is next tie breaker */
466   if (rtp1->rtp_metric.hops < rtp2->rtp_metric.hops) {
467     return true;
468   }
469   if (rtp1->rtp_metric.hops > rtp2->rtp_metric.hops) {
470     return false;
471   }
472
473   /* originator (which is guaranteed to be unique) is final tie breaker */
474   if (memcmp(&rtp1->rtp_originator, &rtp2->rtp_originator, olsr_cnf->ipsize) < 0) {
475     return true;
476   }
477
478   return false;
479 }
480
481 /**
482  * compare the best path of two route entries.
483  *
484  * returns TRUE if the first entry is better
485  * than the second one, FALSE otherwise.
486  */
487 bool
488 olsr_cmp_rt(const struct rt_entry * rt1, const struct rt_entry * rt2)
489 {
490   return olsr_cmp_rtp(rt1->rt_best, rt2->rt_best, NULL);
491 }
492
493 /**
494  * run best route selection among a
495  * set of identical prefixes.
496  */
497 void
498 olsr_rt_best(struct rt_entry *rt)
499 {
500   /* grab the first entry */
501   struct avl_node *node = avl_walk_first(&rt->rt_path_tree);
502
503   assert(node != 0);            /* should not happen */
504
505   rt->rt_best = rtp_tree2rtp(node);
506
507   /* walk all remaining originator entries */
508   while ((node = avl_walk_next(node))) {
509     struct rt_path *rtp = rtp_tree2rtp(node);
510
511     if (olsr_cmp_rtp(rtp, rt->rt_best, current_inetgw)) {
512       rt->rt_best = rtp;
513     }
514   }
515
516   if (0 == rt->rt_dst.prefix_len) {
517     current_inetgw = rt->rt_best;
518   }
519 }
520
521 /**
522  * Insert a prefix into the prefix tree hanging off a lsdb (tc) entry.
523  *
524  * Check if the candidate route (we call this a rt_path) is known,
525  * if not create it.
526  * Upon post-SPF processing (if the node is reachable) the prefix
527  * will be finally inserted into the global RIB.
528  *
529  *@param dst the destination
530  *@param plen the prefix length
531  *@param originator the originating router
532  *@param origin the origin of the route
533  *
534  *@return the new rt_path struct
535  */
536 struct rt_path *
537 olsr_insert_routing_table(union olsr_ip_addr *dst, int plen, union olsr_ip_addr *originator, int origin)
538 {
539 #ifdef DEBUG
540   struct ipaddr_str dstbuf, origbuf;
541 #endif /* DEBUG */
542   struct tc_entry *tc;
543   struct rt_path *rtp;
544   struct avl_node *node;
545   struct olsr_ip_prefix prefix;
546
547   /*
548    * No bogus prefix lengths.
549    */
550   if (plen > olsr_cnf->maxplen) {
551     return NULL;
552   }
553
554   /*
555    * For all routes we use the tc_entry as an hookup point.
556    * If the tc_entry is disconnected, i.e. has no edges it will not
557    * be explored during SPF run.
558    */
559   tc = olsr_locate_tc_entry(originator);
560
561   /*
562    * first check if there is a rt_path for the prefix.
563    */
564   prefix.prefix = *dst;
565   prefix.prefix_len = plen;
566
567   node = avl_find(&tc->prefix_tree, &prefix);
568
569   if (!node) {
570
571     /* no rt_path for this prefix yet */
572     rtp = olsr_alloc_rt_path(tc, &prefix, origin);
573
574     if (!rtp) {
575       return NULL;
576     }
577 #ifdef DEBUG
578     OLSR_PRINTF(1, "RIB: add prefix %s/%u from %s\n", olsr_ip_to_string(&dstbuf, dst), plen,
579                 olsr_ip_to_string(&origbuf, originator));
580 #endif /* DEBUG */
581
582     /* overload the hna change bit for flagging a prefix change */
583     changes_hna = true;
584
585   } else {
586     rtp = rtp_prefix_tree2rtp(node);
587   }
588
589   return rtp;
590 }
591
592 /**
593  * Delete a prefix from the prefix tree hanging off a lsdb (tc) entry.
594  */
595 void
596 olsr_delete_routing_table(union olsr_ip_addr *dst, int plen, union olsr_ip_addr *originator)
597 {
598 #ifdef DEBUG
599   struct ipaddr_str dstbuf, origbuf;
600 #endif /* DEBUG */
601
602   struct tc_entry *tc;
603   struct rt_path *rtp;
604   struct avl_node *node;
605   struct olsr_ip_prefix prefix;
606
607   /*
608    * No bogus prefix lengths.
609    */
610   if (plen > olsr_cnf->maxplen) {
611     return;
612   }
613
614   tc = olsr_lookup_tc_entry(originator);
615   if (!tc) {
616     return;
617   }
618
619   /*
620    * Grab the rt_path for the prefix.
621    */
622   prefix.prefix = *dst;
623   prefix.prefix_len = plen;
624
625   node = avl_find(&tc->prefix_tree, &prefix);
626
627   if (node) {
628     rtp = rtp_prefix_tree2rtp(node);
629     olsr_delete_rt_path(rtp);
630
631 #ifdef DEBUG
632     OLSR_PRINTF(1, "RIB: del prefix %s/%u from %s\n", olsr_ip_to_string(&dstbuf, dst), plen,
633                 olsr_ip_to_string(&origbuf, originator));
634 #endif /* DEBUG */
635
636     /* overload the hna change bit for flagging a prefix change */
637     changes_hna = true;
638   }
639 }
640
641 /**
642  * format a route entry into a buffer
643  */
644 char *
645 olsr_rt_to_string(const struct rt_entry *rt)
646 {
647   static char buff[128];
648   struct ipaddr_str prefixstr, gwstr;
649
650   snprintf(buff, sizeof(buff), "%s/%u via %s", olsr_ip_to_string(&prefixstr, &rt->rt_dst.prefix), rt->rt_dst.prefix_len,
651            olsr_ip_to_string(&gwstr, &rt->rt_nexthop.gateway));
652
653   return buff;
654 }
655
656 /**
657  * format a route path into a buffer
658  */
659 char *
660 olsr_rtp_to_string(const struct rt_path *rtp)
661 {
662   static char buff[128];
663   struct ipaddr_str prefixstr, origstr, gwstr;
664   struct rt_entry *rt = rtp->rtp_rt;
665   struct lqtextbuffer lqbuffer;
666
667   snprintf(buff, sizeof(buff), "%s/%u from %s via %s, " "cost %s, metric %u, v %u",
668            olsr_ip_to_string(&prefixstr, &rt->rt_dst.prefix), rt->rt_dst.prefix_len, olsr_ip_to_string(&origstr,
669                                                                                                        &rtp->rtp_originator),
670            olsr_ip_to_string(&gwstr, &rtp->rtp_nexthop.gateway), get_linkcost_text(rtp->rtp_metric.cost, true, &lqbuffer),
671            rtp->rtp_metric.hops, rtp->rtp_version);
672
673   return buff;
674 }
675
676 /**
677  * Print the routingtree to STDOUT
678  *
679  */
680 #ifndef NODEBUG
681 void
682 olsr_print_routing_table(struct avl_tree *tree)
683 {
684   /* The whole function makes no sense without it. */
685   struct avl_node *rt_tree_node;
686   struct lqtextbuffer lqbuffer;
687
688   OLSR_PRINTF(6, "ROUTING TABLE\n");
689
690   for (rt_tree_node = avl_walk_first(tree); rt_tree_node != NULL; rt_tree_node = avl_walk_next(rt_tree_node)) {
691     struct avl_node *rtp_tree_node;
692     struct ipaddr_str prefixstr, origstr, gwstr;
693     struct rt_entry *rt = rt_tree2rt(rt_tree_node);
694
695     /* first the route entry */
696     OLSR_PRINTF(6, "%s/%u, via %s, best-originator %s\n", olsr_ip_to_string(&prefixstr, &rt->rt_dst.prefix), rt->rt_dst.prefix_len,
697                 olsr_ip_to_string(&origstr, &rt->rt_nexthop.gateway), olsr_ip_to_string(&gwstr, &rt->rt_best->rtp_originator));
698
699     /* walk the per-originator path tree of routes */
700     for (rtp_tree_node = avl_walk_first(&rt->rt_path_tree); rtp_tree_node != NULL; rtp_tree_node = avl_walk_next(rtp_tree_node)) {
701       struct rt_path *rtp = rtp_tree2rtp(rtp_tree_node);
702       OLSR_PRINTF(6, "\tfrom %s, cost %s, metric %u, via %s, %s, v %u\n", olsr_ip_to_string(&origstr, &rtp->rtp_originator),
703                   get_linkcost_text(rtp->rtp_metric.cost, true, &lqbuffer), rtp->rtp_metric.hops, olsr_ip_to_string(&gwstr,
704                                                                                                                     &rtp->
705                                                                                                                     rtp_nexthop.
706                                                                                                                     gateway),
707                   if_ifwithindex_name(rt->rt_nexthop.iif_index), rtp->rtp_version);
708     }
709   }
710   tree = NULL;                  /* squelch compiler warnings */
711 }
712 #endif /* NODEBUG */
713
714 /*
715  * Local Variables:
716  * c-basic-offset: 2
717  * indent-tabs-mode: nil
718  * End:
719  */