PPPoE: use DPO protos in FIB entry path add/remove
[vpp.git] / src / plugins / pppoe / pppoe.c
1 /*
2  *------------------------------------------------------------------
3  * Copyright (c) 2017 Intel and/or its affiliates.
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at:
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *------------------------------------------------------------------
16  */
17 #include <stdint.h>
18 #include <net/if.h>
19 #include <sys/ioctl.h>
20 #include <inttypes.h>
21
22 #include <vlib/vlib.h>
23 #include <vlib/unix/unix.h>
24 #include <vnet/ethernet/ethernet.h>
25 #include <vnet/fib/fib_entry.h>
26 #include <vnet/fib/fib_table.h>
27 #include <vnet/dpo/dpo.h>
28 #include <vnet/plugin/plugin.h>
29 #include <vpp/app/version.h>
30 #include <vnet/ppp/packet.h>
31 #include <pppoe/pppoe.h>
32
33 #include <vppinfra/hash.h>
34 #include <vppinfra/bihash_template.c>
35
36 pppoe_main_t pppoe_main;
37
38 u8 *
39 format_pppoe_session (u8 * s, va_list * args)
40 {
41   pppoe_session_t *t = va_arg (*args, pppoe_session_t *);
42   pppoe_main_t *pem = &pppoe_main;
43
44   s = format (s, "[%d] sw-if-index %d client-ip %U session-id %d ",
45               t - pem->sessions, t->sw_if_index,
46               format_ip46_address, &t->client_ip, IP46_TYPE_ANY,
47               t->session_id);
48
49   s = format (s, "encap-if-index %d decap-fib-index %d\n",
50               t->encap_if_index, t->decap_fib_index);
51
52   s = format (s, "    local-mac %U  client-mac %U",
53               format_ethernet_address, t->local_mac,
54               format_ethernet_address, t->client_mac);
55
56   return s;
57 }
58
59 static u8 *
60 format_pppoe_name (u8 * s, va_list * args)
61 {
62   u32 dev_instance = va_arg (*args, u32);
63   return format (s, "pppoe_session%d", dev_instance);
64 }
65
66 static uword
67 dummy_interface_tx (vlib_main_t * vm,
68                     vlib_node_runtime_t * node, vlib_frame_t * frame)
69 {
70   clib_warning ("you shouldn't be here, leaking buffers...");
71   return frame->n_vectors;
72 }
73
74 static clib_error_t *
75 pppoe_interface_admin_up_down (vnet_main_t * vnm, u32 hw_if_index, u32 flags)
76 {
77   u32 hw_flags = (flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP) ?
78     VNET_HW_INTERFACE_FLAG_LINK_UP : 0;
79   vnet_hw_interface_set_flags (vnm, hw_if_index, hw_flags);
80
81   return /* no error */ 0;
82 }
83
84 /* *INDENT-OFF* */
85 VNET_DEVICE_CLASS (pppoe_device_class,static) = {
86   .name = "PPPPOE",
87   .format_device_name = format_pppoe_name,
88   .format_tx_trace = format_pppoe_encap_trace,
89   .tx_function = dummy_interface_tx,
90   .admin_up_down_function = pppoe_interface_admin_up_down,
91 };
92 /* *INDENT-ON* */
93
94 static u8 *
95 format_pppoe_header_with_length (u8 * s, va_list * args)
96 {
97   u32 dev_instance = va_arg (*args, u32);
98   s = format (s, "unimplemented dev %u", dev_instance);
99   return s;
100 }
101
102 /* *INDENT-OFF* */
103 VNET_HW_INTERFACE_CLASS (pppoe_hw_class) =
104 {
105   .name = "PPPPOE",
106   .format_header = format_pppoe_header_with_length,
107   .build_rewrite = default_build_rewrite,
108   .flags = VNET_HW_INTERFACE_CLASS_FLAG_P2P,
109 };
110 /* *INDENT-ON* */
111
112 #define foreach_copy_field                      \
113 _(session_id)                                   \
114 _(encap_if_index)                               \
115 _(decap_fib_index)                              \
116 _(client_ip)
117
118 static void
119 eth_pppoe_rewrite (pppoe_session_t * t, bool is_ip6)
120 {
121   u8 *rw = 0;
122   int len = sizeof (pppoe_header_t) + sizeof (ethernet_header_t);
123
124   vec_validate_aligned (rw, len - 1, CLIB_CACHE_LINE_BYTES);
125
126   ethernet_header_t *eth_hdr = (ethernet_header_t *) rw;
127   clib_memcpy (eth_hdr->dst_address, t->client_mac, 6);
128   clib_memcpy (eth_hdr->src_address, t->local_mac, 6);
129   eth_hdr->type = clib_host_to_net_u16 (ETHERNET_TYPE_PPPOE_SESSION);
130
131   pppoe_header_t *pppoe = (pppoe_header_t *) (eth_hdr + 1);
132   pppoe->ver_type = PPPOE_VER_TYPE;
133   pppoe->code = 0;
134   pppoe->session_id = clib_host_to_net_u16 (t->session_id);
135   pppoe->length = 0;            /* To be filled in at run-time */
136
137   if (!is_ip6)
138     {
139       pppoe->ppp_proto = clib_host_to_net_u16 (PPP_PROTOCOL_ip4);
140     }
141   else
142     {
143       pppoe->ppp_proto = clib_host_to_net_u16 (PPP_PROTOCOL_ip6);
144     }
145
146   t->rewrite = rw;
147   _vec_len (t->rewrite) = len;
148
149   return;
150 }
151
152 static bool
153 pppoe_decap_next_is_valid (pppoe_main_t * pem, u32 is_ip6,
154                            u32 decap_fib_index)
155 {
156   vlib_main_t *vm = pem->vlib_main;
157   u32 input_idx = (!is_ip6) ? ip4_input_node.index : ip6_input_node.index;
158   vlib_node_runtime_t *r = vlib_node_get_runtime (vm, input_idx);
159
160   return decap_fib_index < r->n_next_nodes;
161 }
162
163 int vnet_pppoe_add_del_session
164   (vnet_pppoe_add_del_session_args_t * a, u32 * sw_if_indexp)
165 {
166   pppoe_main_t *pem = &pppoe_main;
167   pppoe_session_t *t = 0;
168   vnet_main_t *vnm = pem->vnet_main;
169   u32 hw_if_index = ~0;
170   u32 sw_if_index = ~0;
171   u32 is_ip6 = a->is_ip6;
172   pppoe_entry_key_t cached_key;
173   pppoe_entry_result_t cached_result;
174   u32 bucket;
175   pppoe_entry_key_t key;
176   pppoe_entry_result_t result;
177   vnet_hw_interface_t *hi;
178   vnet_sw_interface_t *si;
179   fib_prefix_t pfx;
180
181   cached_key.raw = ~0;
182   cached_result.raw = ~0;       /* warning be gone */
183   memset (&pfx, 0, sizeof (pfx));
184
185   if (!is_ip6)
186     {
187       pfx.fp_addr.ip4.as_u32 = a->client_ip.ip4.as_u32;
188       pfx.fp_len = 32;
189       pfx.fp_proto = FIB_PROTOCOL_IP4;
190     }
191   else
192     {
193       pfx.fp_addr.ip6.as_u64[0] = a->client_ip.ip6.as_u64[0];
194       pfx.fp_addr.ip6.as_u64[1] = a->client_ip.ip6.as_u64[1];
195       pfx.fp_len = 128;
196       pfx.fp_proto = FIB_PROTOCOL_IP6;
197     }
198
199   /* Get encap_if_index and local mac address */
200   pppoe_lookup_1 (&pem->session_table, &cached_key, &cached_result,
201                   a->client_mac, clib_host_to_net_u16 (a->session_id),
202                   &key, &bucket, &result);
203   a->encap_if_index = result.fields.sw_if_index;
204
205   if (a->encap_if_index == ~0)
206     return VNET_API_ERROR_INVALID_SW_IF_INDEX;
207
208   si = vnet_get_sw_interface (vnm, a->encap_if_index);
209   hi = vnet_get_hw_interface (vnm, si->hw_if_index);
210
211
212   if (a->is_add)
213     {
214       /* adding a session: session must not already exist */
215       if (result.fields.session_index != ~0)
216         return VNET_API_ERROR_TUNNEL_EXIST;
217
218       /*if not set explicitly, default to ip4 */
219       if (!pppoe_decap_next_is_valid (pem, is_ip6, a->decap_fib_index))
220         return VNET_API_ERROR_INVALID_DECAP_NEXT;
221
222       pool_get_aligned (pem->sessions, t, CLIB_CACHE_LINE_BYTES);
223       memset (t, 0, sizeof (*t));
224
225       clib_memcpy (t->local_mac, hi->hw_address, 6);
226
227       /* copy from arg structure */
228 #define _(x) t->x = a->x;
229       foreach_copy_field;
230 #undef _
231
232       clib_memcpy (t->client_mac, a->client_mac, 6);
233
234       eth_pppoe_rewrite (t, is_ip6);
235
236       /* update pppoe fib with session_index */
237       result.fields.session_index = t - pem->sessions;
238       pppoe_update_1 (&pem->session_table,
239                       a->client_mac, clib_host_to_net_u16 (a->session_id),
240                       &key, &bucket, &result);
241
242       vnet_hw_interface_t *hi;
243       if (vec_len (pem->free_pppoe_session_hw_if_indices) > 0)
244         {
245           vnet_interface_main_t *im = &vnm->interface_main;
246           hw_if_index = pem->free_pppoe_session_hw_if_indices
247             [vec_len (pem->free_pppoe_session_hw_if_indices) - 1];
248           _vec_len (pem->free_pppoe_session_hw_if_indices) -= 1;
249
250           hi = vnet_get_hw_interface (vnm, hw_if_index);
251           hi->dev_instance = t - pem->sessions;
252           hi->hw_instance = hi->dev_instance;
253
254           /* clear old stats of freed session before reuse */
255           sw_if_index = hi->sw_if_index;
256           vnet_interface_counter_lock (im);
257           vlib_zero_combined_counter
258             (&im->combined_sw_if_counters[VNET_INTERFACE_COUNTER_TX],
259              sw_if_index);
260           vlib_zero_combined_counter (&im->combined_sw_if_counters
261                                       [VNET_INTERFACE_COUNTER_RX],
262                                       sw_if_index);
263           vlib_zero_simple_counter (&im->sw_if_counters
264                                     [VNET_INTERFACE_COUNTER_DROP],
265                                     sw_if_index);
266           vnet_interface_counter_unlock (im);
267         }
268       else
269         {
270           hw_if_index = vnet_register_interface
271             (vnm, pppoe_device_class.index, t - pem->sessions,
272              pppoe_hw_class.index, t - pem->sessions);
273           hi = vnet_get_hw_interface (vnm, hw_if_index);
274         }
275
276       t->hw_if_index = hw_if_index;
277       t->sw_if_index = sw_if_index = hi->sw_if_index;
278
279       vec_validate_init_empty (pem->session_index_by_sw_if_index, sw_if_index,
280                                ~0);
281       pem->session_index_by_sw_if_index[sw_if_index] = t - pem->sessions;
282
283       vnet_sw_interface_t *si = vnet_get_sw_interface (vnm, sw_if_index);
284       si->flags &= ~VNET_SW_INTERFACE_FLAG_HIDDEN;
285       vnet_sw_interface_set_flags (vnm, sw_if_index,
286                                    VNET_SW_INTERFACE_FLAG_ADMIN_UP);
287
288       /* Set pppoe session output node */
289       hi->output_node_index = pppoe_encap_node.index;
290
291       /* add reverse route for client ip */
292       fib_table_entry_path_add (a->decap_fib_index, &pfx,
293                                 FIB_SOURCE_PLUGIN_HI, FIB_ENTRY_FLAG_NONE,
294                                 fib_proto_to_dpo (pfx.fp_proto),
295                                 &pfx.fp_addr, sw_if_index, ~0,
296                                 1, NULL, FIB_ROUTE_PATH_FLAG_NONE);
297
298     }
299   else
300     {
301       /* deleting a session: session must exist */
302       if (result.fields.session_index == ~0)
303         return VNET_API_ERROR_NO_SUCH_ENTRY;
304
305       t = pool_elt_at_index (pem->sessions, result.fields.session_index);
306       sw_if_index = t->sw_if_index;
307
308       vnet_sw_interface_set_flags (vnm, t->sw_if_index, 0 /* down */ );
309       vnet_sw_interface_t *si = vnet_get_sw_interface (vnm, t->sw_if_index);
310       si->flags |= VNET_SW_INTERFACE_FLAG_HIDDEN;
311
312       vec_add1 (pem->free_pppoe_session_hw_if_indices, t->hw_if_index);
313
314       pem->session_index_by_sw_if_index[t->sw_if_index] = ~0;
315
316       /* update pppoe fib with session_inde=~0x */
317       result.fields.session_index = ~0;
318       pppoe_update_1 (&pem->session_table,
319                       a->client_mac, clib_host_to_net_u16 (a->session_id),
320                       &key, &bucket, &result);
321
322
323       /* delete reverse route for client ip */
324       fib_table_entry_path_remove (a->decap_fib_index, &pfx,
325                                    FIB_SOURCE_PLUGIN_HI,
326                                    fib_proto_to_dpo (pfx.fp_proto),
327                                    &pfx.fp_addr,
328                                    sw_if_index, ~0, 1,
329                                    FIB_ROUTE_PATH_FLAG_NONE);
330
331       vec_free (t->rewrite);
332       pool_put (pem->sessions, t);
333     }
334
335   if (sw_if_indexp)
336     *sw_if_indexp = sw_if_index;
337
338   return 0;
339 }
340
341 static clib_error_t *
342 pppoe_add_del_session_command_fn (vlib_main_t * vm,
343                                   unformat_input_t * input,
344                                   vlib_cli_command_t * cmd)
345 {
346   unformat_input_t _line_input, *line_input = &_line_input;
347   u16 session_id = 0;
348   ip46_address_t client_ip;
349   u8 is_add = 1;
350   u8 client_ip_set = 0;
351   u8 ipv4_set = 0;
352   u8 ipv6_set = 0;
353   u32 encap_if_index = 0;
354   u32 decap_fib_index = 0;
355   u8 client_mac[6] = { 0 };
356   u8 client_mac_set = 0;
357   int rv;
358   u32 tmp;
359   vnet_pppoe_add_del_session_args_t _a, *a = &_a;
360   u32 session_sw_if_index;
361   clib_error_t *error = NULL;
362
363   /* Cant "universally zero init" (={0}) due to GCC bug 53119 */
364   memset (&client_ip, 0, sizeof client_ip);
365
366   /* Get a line of input. */
367   if (!unformat_user (input, unformat_line_input, line_input))
368     return 0;
369
370   while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
371     {
372       if (unformat (line_input, "del"))
373         {
374           is_add = 0;
375         }
376       else if (unformat (line_input, "session-id %d", &session_id))
377         ;
378       else if (unformat (line_input, "client-ip %U",
379                          unformat_ip4_address, &client_ip.ip4))
380         {
381           client_ip_set = 1;
382           ipv4_set = 1;
383         }
384       else if (unformat (line_input, "client-ip %U",
385                          unformat_ip6_address, &client_ip.ip6))
386         {
387           client_ip_set = 1;
388           ipv6_set = 1;
389         }
390       else if (unformat (line_input, "decap-vrf-id %d", &tmp))
391         {
392           if (ipv6_set)
393             decap_fib_index = fib_table_find (FIB_PROTOCOL_IP6, tmp);
394           else
395             decap_fib_index = fib_table_find (FIB_PROTOCOL_IP4, tmp);
396
397           if (decap_fib_index == ~0)
398             {
399               error =
400                 clib_error_return (0, "nonexistent decap fib id %d", tmp);
401               goto done;
402             }
403         }
404       else
405         if (unformat
406             (line_input, "client-mac %U", unformat_ethernet_address,
407              client_mac))
408         client_mac_set = 1;
409       else
410         {
411           error = clib_error_return (0, "parse error: '%U'",
412                                      format_unformat_error, line_input);
413           goto done;
414         }
415     }
416
417   if (client_ip_set == 0)
418     {
419       error =
420         clib_error_return (0, "session client ip address not specified");
421       goto done;
422     }
423
424   if (ipv4_set && ipv6_set)
425     {
426       error = clib_error_return (0, "both IPv4 and IPv6 addresses specified");
427       goto done;
428     }
429
430   if (client_mac_set == 0)
431     {
432       error = clib_error_return (0, "session client mac not specified");
433       goto done;
434     }
435
436   memset (a, 0, sizeof (*a));
437
438   a->is_add = is_add;
439   a->is_ip6 = ipv6_set;
440
441 #define _(x) a->x = x;
442   foreach_copy_field;
443 #undef _
444
445   clib_memcpy (a->client_mac, client_mac, 6);
446
447   rv = vnet_pppoe_add_del_session (a, &session_sw_if_index);
448
449   switch (rv)
450     {
451     case 0:
452       if (is_add)
453         vlib_cli_output (vm, "%U\n", format_vnet_sw_if_index_name,
454                          vnet_get_main (), session_sw_if_index);
455       break;
456
457     case VNET_API_ERROR_TUNNEL_EXIST:
458       error = clib_error_return (0, "session already exists...");
459       goto done;
460
461     case VNET_API_ERROR_NO_SUCH_ENTRY:
462       error = clib_error_return (0, "session does not exist...");
463       goto done;
464
465     default:
466       error = clib_error_return
467         (0, "vnet_pppoe_add_del_session returned %d", rv);
468       goto done;
469     }
470
471 done:
472   unformat_free (line_input);
473
474   return error;
475 }
476
477 /*?
478  * Add or delete a PPPPOE Session.
479  *
480  * @cliexpar
481  * Example of how to create a PPPPOE Session:
482  * @cliexcmd{create pppoe session client-ip 10.0.3.1 session-id 13
483  *             client-mac 00:01:02:03:04:05 }
484  * Example of how to delete a PPPPOE Session:
485  * @cliexcmd{create pppoe session client-ip 10.0.3.1 session-id 13
486  *             client-mac 00:01:02:03:04:05 del }
487  ?*/
488 /* *INDENT-OFF* */
489 VLIB_CLI_COMMAND (create_pppoe_session_command, static) = {
490   .path = "create pppoe session",
491   .short_help =
492   "create pppoe session client-ip <client-ip> session-id <nn>"
493   " client-mac <client-mac> [decap-vrf-id <nn>] [del]",
494   .function = pppoe_add_del_session_command_fn,
495 };
496 /* *INDENT-ON* */
497
498 /* *INDENT-OFF* */
499 static clib_error_t *
500 show_pppoe_session_command_fn (vlib_main_t * vm,
501                                unformat_input_t * input,
502                                vlib_cli_command_t * cmd)
503 {
504   pppoe_main_t *pem = &pppoe_main;
505   pppoe_session_t *t;
506
507   if (pool_elts (pem->sessions) == 0)
508     vlib_cli_output (vm, "No pppoe sessions configured...");
509
510   pool_foreach (t, pem->sessions,
511                 ({
512                     vlib_cli_output (vm, "%U",format_pppoe_session, t);
513                 }));
514
515   return 0;
516 }
517 /* *INDENT-ON* */
518
519 /*?
520  * Display all the PPPPOE Session entries.
521  *
522  * @cliexpar
523  * Example of how to display the PPPPOE Session entries:
524  * @cliexstart{show pppoe session}
525  * [0] client-ip 10.0.3.1 session_id 13 encap-if-index 0 decap-vrf-id 13 sw_if_index 5
526  *     local-mac a0:b0:c0:d0:e0:f0 client-mac 00:01:02:03:04:05
527  * @cliexend
528  ?*/
529 /* *INDENT-OFF* */
530 VLIB_CLI_COMMAND (show_pppoe_session_command, static) = {
531     .path = "show pppoe session",
532     .short_help = "show pppoe session",
533     .function = show_pppoe_session_command_fn,
534 };
535 /* *INDENT-ON* */
536
537 /** Display the contents of the PPPoE Fib. */
538 static clib_error_t *
539 show_pppoe_fib_command_fn (vlib_main_t * vm,
540                            unformat_input_t * input, vlib_cli_command_t * cmd)
541 {
542   pppoe_main_t *pem = &pppoe_main;
543   BVT (clib_bihash) * h = &pem->session_table;
544   BVT (clib_bihash_bucket) * b;
545   BVT (clib_bihash_value) * v;
546   pppoe_entry_key_t key;
547   pppoe_entry_result_t result;
548   u32 first_entry = 1;
549   u64 total_entries = 0;
550   int i, j, k;
551   u8 *s = 0;
552
553   for (i = 0; i < h->nbuckets; i++)
554     {
555       b = &h->buckets[i];
556       if (b->offset == 0)
557         continue;
558       v = BV (clib_bihash_get_value) (h, b->offset);
559       for (j = 0; j < (1 << b->log2_pages); j++)
560         {
561           for (k = 0; k < BIHASH_KVP_PER_PAGE; k++)
562             {
563               if (v->kvp[k].key == ~0ULL && v->kvp[k].value == ~0ULL)
564                 continue;
565
566               if (first_entry)
567                 {
568                   first_entry = 0;
569                   vlib_cli_output (vm,
570                                    "%=19s%=12s%=13s%=14s",
571                                    "Mac-Address", "session_id", "sw_if_index",
572                                    "session_index");
573                 }
574
575               key.raw = v->kvp[k].key;
576               result.raw = v->kvp[k].value;
577
578
579               vlib_cli_output (vm,
580                                "%=19U%=12d%=13d%=14d",
581                                format_ethernet_address, key.fields.mac,
582                                clib_net_to_host_u16 (key.fields.session_id),
583                                result.fields.sw_if_index == ~0
584                                ? -1 : result.fields.sw_if_index,
585                                result.fields.session_index == ~0
586                                ? -1 : result.fields.session_index);
587               vec_reset_length (s);
588               total_entries++;
589             }
590           v++;
591         }
592     }
593
594   if (total_entries == 0)
595     vlib_cli_output (vm, "no pppoe fib entries");
596   else
597     vlib_cli_output (vm, "%lld pppoe fib entries", total_entries);
598
599   vec_free (s);
600   return 0;
601 }
602
603 /*?
604  * This command dispays the MAC Address entries of the PPPoE FIB table.
605  * Output can be filtered to just get the number of MAC Addresses or display
606  * each MAC Address.
607  *
608  * @cliexpar
609  * Example of how to display the number of MAC Address entries in the PPPoE
610  * FIB table:
611  * @cliexstart{show pppoe fib}
612  *     Mac Address      session_id      Interface           sw_if_index  session_index
613  *  52:54:00:53:18:33     1          GigabitEthernet0/8/0        2          0
614  *  52:54:00:53:18:55     2          GigabitEthernet0/8/1        3          1
615  * @cliexend
616 ?*/
617 /* *INDENT-OFF* */
618 VLIB_CLI_COMMAND (show_pppoe_fib_command, static) = {
619     .path = "show pppoe fib",
620     .short_help = "show pppoe fib",
621     .function = show_pppoe_fib_command_fn,
622 };
623 /* *INDENT-ON* */
624
625 clib_error_t *
626 pppoe_init (vlib_main_t * vm)
627 {
628   pppoe_main_t *pem = &pppoe_main;
629
630   pem->vnet_main = vnet_get_main ();
631   pem->vlib_main = vm;
632
633   /* Create the hash table  */
634   BV (clib_bihash_init) (&pem->session_table, "pppoe session table",
635                          PPPOE_NUM_BUCKETS, PPPOE_MEMORY_SIZE);
636
637   ethernet_register_input_type (vm, ETHERNET_TYPE_PPPOE_SESSION,
638                                 pppoe_input_node.index);
639
640   ethernet_register_input_type (vm, ETHERNET_TYPE_PPPOE_DISCOVERY,
641                                 pppoe_tap_dispatch_node.index);
642
643   return 0;
644 }
645
646 VLIB_INIT_FUNCTION (pppoe_init);
647
648 /* *INDENT-OFF* */
649 VLIB_PLUGIN_REGISTER () = {
650     .version = VPP_BUILD_VER,
651     .description = "PPPoE",
652 };
653 /* *INDENT-ON* */
654
655 /*
656  * fd.io coding-style-patch-verification: ON
657  *
658  * Local Variables:
659  * eval: (c-set-style "gnu")
660  * End:
661  */