Punt: socket register for exception dispatched/punted packets based on reason
[vpp.git] / src / vnet / ip / punt.c
1 /*
2  * Copyright (c) 2016 Cisco and/or its affiliates.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at:
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15
16 /**
17  * @file
18  * @brief Local TCP/IP stack punt infrastructure.
19  *
20  * Provides a set of VPP nodes together with the relevant APIs and CLI
21  * commands in order to adjust and dispatch packets from the VPP data plane
22  * to the local TCP/IP stack
23  */
24
25 #include <vnet/ip/ip.h>
26 #include <vlib/vlib.h>
27 #include <vnet/pg/pg.h>
28 #include <vnet/udp/udp.h>
29 #include <vnet/tcp/tcp.h>
30 #include <vnet/sctp/sctp.h>
31 #include <vnet/ip/punt.h>
32 #include <vlib/unix/unix.h>
33
34 #include <stdio.h>
35 #include <unistd.h>
36 #include <sys/socket.h>
37 #include <sys/uio.h>
38 #include <stdlib.h>
39
40 punt_main_t punt_main;
41
42 char *
43 vnet_punt_get_server_pathname (void)
44 {
45   punt_main_t *pm = &punt_main;
46   return pm->sun_path;
47 }
48
49 static void
50 punt_client_l4_db_add (ip_address_family_t af, u16 port, u32 index)
51 {
52   punt_main_t *pm = &punt_main;
53
54   pm->db.clients_by_l4_port = hash_set (pm->db.clients_by_l4_port,
55                                         punt_client_l4_mk_key (af, port),
56                                         index);
57 }
58
59 static u32
60 punt_client_l4_db_remove (ip_address_family_t af, u16 port)
61 {
62   punt_main_t *pm = &punt_main;
63   u32 key, index = ~0;
64   uword *p;
65
66   key = punt_client_l4_mk_key (af, port);
67   p = hash_get (pm->db.clients_by_l4_port, key);
68
69   if (p)
70     index = p[0];
71
72   hash_unset (pm->db.clients_by_l4_port, key);
73
74   return (index);
75 }
76
77 static void
78 punt_client_exception_db_add (vlib_punt_reason_t reason, u32 pci)
79 {
80   punt_main_t *pm = &punt_main;
81
82   vec_validate_init_empty (pm->db.clients_by_exception, reason, ~0);
83
84   pm->db.clients_by_exception[reason] = pci;
85 }
86
87 static u32
88 punt_client_exception_db_remove (vlib_punt_reason_t reason)
89 {
90   punt_main_t *pm = &punt_main;
91   u32 pci = ~0;
92
93   if (punt_client_exception_get (reason))
94     {
95       pci = pm->db.clients_by_exception[reason];
96       pm->db.clients_by_exception[reason] = ~0;
97     }
98
99   return pci;
100 }
101
102 static clib_error_t *
103 punt_socket_read_ready (clib_file_t * uf)
104 {
105   vlib_main_t *vm = vlib_get_main ();
106   punt_main_t *pm = &punt_main;
107
108   /** Schedule the rx node */
109   vlib_node_set_interrupt_pending (vm, punt_socket_rx_node.index);
110   vec_add1 (pm->ready_fds, uf->file_descriptor);
111
112   return 0;
113 }
114
115 static clib_error_t *
116 punt_socket_register_l4 (vlib_main_t * vm,
117                          ip_address_family_t af,
118                          u8 protocol, u16 port, char *client_pathname)
119 {
120   punt_main_t *pm = &punt_main;
121   punt_client_t *c;
122
123   /* For now we only support UDP punt */
124   if (protocol != IP_PROTOCOL_UDP)
125     return clib_error_return (0,
126                               "only UDP protocol (%d) is supported, got %d",
127                               IP_PROTOCOL_UDP, protocol);
128
129   if (port == (u16) ~ 0)
130     return clib_error_return (0, "UDP port number required");
131
132   if (strncmp (client_pathname, vnet_punt_get_server_pathname (),
133                UNIX_PATH_MAX) == 0)
134     return clib_error_return (0,
135                               "Punt socket: Invalid client path: %s",
136                               client_pathname);
137
138   c = punt_client_l4_get (af, port);
139
140   if (NULL == c)
141     {
142       pool_get_zero (pm->punt_client_pool, c);
143       punt_client_l4_db_add (af, port, c - pm->punt_client_pool);
144     }
145
146   memcpy (c->caddr.sun_path, client_pathname, sizeof (c->caddr.sun_path));
147   c->caddr.sun_family = AF_UNIX;
148   c->reg.type = PUNT_TYPE_L4;
149   c->reg.punt.l4.port = port;
150   c->reg.punt.l4.protocol = protocol;
151   c->reg.punt.l4.af = af;
152
153   u32 node_index = (af == AF_IP4 ?
154                     udp4_punt_socket_node.index :
155                     udp6_punt_socket_node.index);
156
157   udp_register_dst_port (vm, port, node_index, af == AF_IP4);
158
159   return (NULL);
160 }
161
162 static clib_error_t *
163 punt_socket_register_exception (vlib_main_t * vm,
164                                 vlib_punt_reason_t reason,
165                                 char *client_pathname)
166 {
167   punt_main_t *pm = &punt_main;
168   punt_client_t *pc;
169
170   pc = punt_client_exception_get (reason);
171
172   if (NULL == pc)
173     {
174       pool_get_zero (pm->punt_client_pool, pc);
175       punt_client_exception_db_add (reason, pc - pm->punt_client_pool);
176     }
177
178   memcpy (pc->caddr.sun_path, client_pathname, sizeof (pc->caddr.sun_path));
179   pc->caddr.sun_family = AF_UNIX;
180   pc->reg.type = PUNT_TYPE_EXCEPTION;
181   pc->reg.punt.exception.reason = reason;
182
183   vlib_punt_register (pm->hdl,
184                       pc->reg.punt.exception.reason, "exception-punt-socket");
185
186   return (NULL);
187 }
188
189 static clib_error_t *
190 punt_socket_unregister_l4 (ip_address_family_t af,
191                            ip_protocol_t protocol, u16 port)
192 {
193   u32 pci;
194
195   udp_unregister_dst_port (vlib_get_main (), port, af == AF_IP4);
196
197   pci = punt_client_l4_db_remove (af, port);
198
199   if (~0 != pci)
200     pool_put_index (punt_main.punt_client_pool, pci);
201
202   return (NULL);
203 }
204
205 static clib_error_t *
206 punt_socket_unregister_exception (vlib_punt_reason_t reason)
207 {
208   u32 pci;
209
210   pci = punt_client_exception_db_remove (reason);
211
212   if (~0 != pci)
213     pool_put_index (punt_main.punt_client_pool, pci);
214
215   return (NULL);
216 }
217
218 clib_error_t *
219 vnet_punt_socket_add (vlib_main_t * vm, u32 header_version,
220                       const punt_reg_t * pr, char *client_pathname)
221 {
222   punt_main_t *pm = &punt_main;
223
224   if (!pm->is_configured)
225     return clib_error_return (0, "socket is not configured");
226
227   if (header_version != PUNT_PACKETDESC_VERSION)
228     return clib_error_return (0, "Invalid packet descriptor version");
229
230   /* Register client */
231   switch (pr->type)
232     {
233     case PUNT_TYPE_L4:
234       return (punt_socket_register_l4 (vm,
235                                        pr->punt.l4.af,
236                                        pr->punt.l4.protocol,
237                                        pr->punt.l4.port, client_pathname));
238     case PUNT_TYPE_EXCEPTION:
239       return (punt_socket_register_exception (vm,
240                                               pr->punt.exception.reason,
241                                               client_pathname));
242     }
243
244   return 0;
245 }
246
247 clib_error_t *
248 vnet_punt_socket_del (vlib_main_t * vm, const punt_reg_t * pr)
249 {
250   punt_main_t *pm = &punt_main;
251
252   if (!pm->is_configured)
253     return clib_error_return (0, "socket is not configured");
254
255   switch (pr->type)
256     {
257     case PUNT_TYPE_L4:
258       return (punt_socket_unregister_l4 (pr->punt.l4.af,
259                                          pr->punt.l4.protocol,
260                                          pr->punt.l4.port));
261     case PUNT_TYPE_EXCEPTION:
262       return (punt_socket_unregister_exception (pr->punt.exception.reason));
263     }
264
265   return 0;
266 }
267
268 /**
269  * @brief Request IP traffic punt to the local TCP/IP stack.
270  *
271  * @em Note
272  * - UDP, TCP and SCTP are the only protocols supported in the current implementation
273  *
274  * @param vm       vlib_main_t corresponding to the current thread
275  * @param af       IP address family.
276  * @param protocol 8-bits L4 protocol value
277  *                 UDP is 17
278  *                 TCP is 1
279  * @param port     16-bits L4 (TCP/IP) port number when applicable (UDP only)
280  *
281  * @returns 0 on success, non-zero value otherwise
282  */
283 static clib_error_t *
284 punt_l4_add_del (vlib_main_t * vm,
285                  ip_address_family_t af,
286                  ip_protocol_t protocol, u16 port, bool is_add)
287 {
288   /* For now we only support TCP, UDP and SCTP punt */
289   if (protocol != IP_PROTOCOL_UDP &&
290       protocol != IP_PROTOCOL_TCP && protocol != IP_PROTOCOL_SCTP)
291     return clib_error_return (0,
292                               "only UDP (%d), TCP (%d) and SCTP (%d) protocols are supported, got %d",
293                               IP_PROTOCOL_UDP, IP_PROTOCOL_TCP,
294                               IP_PROTOCOL_SCTP, protocol);
295
296   if (port == (u16) ~ 0)
297     {
298       if (protocol == IP_PROTOCOL_UDP)
299         udp_punt_unknown (vm, af == AF_IP4, is_add);
300       else if (protocol == IP_PROTOCOL_TCP)
301         tcp_punt_unknown (vm, af == AF_IP4, is_add);
302       else if (protocol == IP_PROTOCOL_SCTP)
303         sctp_punt_unknown (vm, af == AF_IP4, is_add);
304
305       return 0;
306     }
307
308   else if (is_add)
309     {
310       if (protocol == IP_PROTOCOL_TCP || protocol == IP_PROTOCOL_SCTP)
311         return clib_error_return (0,
312                                   "punt TCP/SCTP ports is not supported yet");
313
314       if (!udp_is_valid_dst_port (port, af == AF_IP4))
315         return clib_error_return (0, "invalid port: %d", port);
316
317       udp_register_dst_port (vm, port, udp4_punt_node.index, af == AF_IP4);
318
319       return 0;
320     }
321   else
322     {
323       if (protocol == IP_PROTOCOL_TCP || protocol == IP_PROTOCOL_SCTP)
324         return clib_error_return (0,
325                                   "punt TCP/SCTP ports is not supported yet");
326
327       udp_unregister_dst_port (vm, port, af == AF_IP4);
328
329       return 0;
330     }
331 }
332
333 static clib_error_t *
334 punt_exception_add_del (vlib_main_t * vm,
335                         vlib_punt_reason_t reason, bool is_add)
336 {
337   return (NULL);
338 }
339
340 clib_error_t *
341 vnet_punt_add_del (vlib_main_t * vm, const punt_reg_t * pr, bool is_add)
342 {
343   switch (pr->type)
344     {
345     case PUNT_TYPE_L4:
346       return (punt_l4_add_del (vm, pr->punt.l4.af, pr->punt.l4.protocol,
347                                pr->punt.l4.port, is_add));
348     case PUNT_TYPE_EXCEPTION:
349       return (punt_exception_add_del (vm, pr->punt.exception.reason, is_add));
350     }
351
352   return (clib_error_return (0, "Unsupported punt type: %d", pr->type));
353 }
354
355 static clib_error_t *
356 punt_cli (vlib_main_t * vm,
357           unformat_input_t * input, vlib_cli_command_t * cmd)
358 {
359   clib_error_t *error = NULL;
360   bool is_add = true;
361   punt_reg_t pr = {
362     .punt = {
363              .l4 = {
364                     .af = AF_IP4,
365                     .port = ~0,
366                     .protocol = ~0,
367                     },
368              },
369     .type = PUNT_TYPE_L4,
370   };
371
372   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
373     {
374       if (unformat (input, "del"))
375         is_add = false;
376       else if (unformat (input, "ipv6"))
377         pr.punt.l4.af = AF_IP6;
378       else if (unformat (input, "ip6"))
379         pr.punt.l4.af = AF_IP6;
380       else if (unformat (input, "%d", &pr.punt.l4.port))
381         ;
382       else if (unformat (input, "udp"))
383         pr.punt.l4.protocol = IP_PROTOCOL_UDP;
384       else if (unformat (input, "tcp"))
385         pr.punt.l4.protocol = IP_PROTOCOL_TCP;
386       else
387         {
388           error = clib_error_return (0, "parse error: '%U'",
389                                      format_unformat_error, input);
390           goto done;
391         }
392     }
393
394   /* punt both IPv6 and IPv4 when used in CLI */
395   error = vnet_punt_add_del (vm, &pr, is_add);
396   if (error)
397     {
398       clib_error_report (error);
399     }
400
401 done:
402   return error;
403 }
404
405 /*?
406  * The set of '<em>set punt</em>' commands allows specific IP traffic to
407  * be punted to the host TCP/IP stack
408  *
409  * @em Note
410  * - UDP is the only protocol supported in the current implementation
411  * - All TCP traffic is currently punted to the host by default
412  *
413  * @cliexpar
414  * @parblock
415  * Example of how to request NTP traffic to be punted
416  * @cliexcmd{set punt udp 125}
417  *
418  * Example of how to request all 'unknown' UDP traffic to be punted
419  * @cliexcmd{set punt udp all}
420  *
421  * Example of how to stop all 'unknown' UDP traffic to be punted
422  * @cliexcmd{set punt udp del all}
423  * @endparblock
424 ?*/
425 /* *INDENT-OFF* */
426 VLIB_CLI_COMMAND (punt_command, static) = {
427   .path = "set punt",
428   .short_help = "set punt [udp|tcp] [del] <all | port-num1 [port-num2 ...]>",
429   .function = punt_cli,
430 };
431 /* *INDENT-ON* */
432
433 static clib_error_t *
434 punt_socket_register_cmd (vlib_main_t * vm,
435                           unformat_input_t * input, vlib_cli_command_t * cmd)
436 {
437   u8 *socket_name = 0;
438   clib_error_t *error = NULL;
439   /* *INDENT-OFF* */
440   punt_reg_t pr = {
441     .punt = {
442       .l4 = {
443         .af = AF_IP4,
444         .port = ~0,
445         .protocol = ~0,
446       },
447     },
448     .type = PUNT_TYPE_L4,
449   };
450   /* *INDENT-ON* */
451
452   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
453     {
454       if (unformat (input, "ipv4"))
455         ;
456       else if (unformat (input, "ipv6"))
457         pr.punt.l4.af = AF_IP6;
458       else if (unformat (input, "udp"))
459         pr.punt.l4.protocol = IP_PROTOCOL_UDP;
460       else if (unformat (input, "tcp"))
461         pr.punt.l4.protocol = IP_PROTOCOL_TCP;
462       else if (unformat (input, "%d", &pr.punt.l4.port))
463         ;
464       else if (unformat (input, "socket %s", &socket_name))
465         ;
466       else
467         {
468           error = clib_error_return (0, "parse error: '%U'",
469                                      format_unformat_error, input);
470           goto done;
471         }
472     }
473
474   error = vnet_punt_socket_add (vm, 1, &pr, (char *) socket_name);
475
476 done:
477   return error;
478 }
479
480 /*?
481  *
482  * @cliexpar
483  * @cliexcmd{punt socket register}
484  ?*/
485 /* *INDENT-OFF* */
486 VLIB_CLI_COMMAND (punt_socket_register_command, static) =
487 {
488   .path = "punt socket register",
489   .function = punt_socket_register_cmd,
490   .short_help = "punt socket register [ipv4|ipv6] [udp|tcp]> <all | port-num1 [port-num2 ...]> <socket>",
491   .is_mp_safe = 1,
492 };
493 /* *INDENT-ON* */
494
495 static clib_error_t *
496 punt_socket_deregister_cmd (vlib_main_t * vm,
497                             unformat_input_t * input,
498                             vlib_cli_command_t * cmd)
499 {
500   clib_error_t *error = NULL;
501   /* *INDENT-OFF* */
502   punt_reg_t pr = {
503     .punt = {
504       .l4 = {
505         .af = AF_IP4,
506         .port = ~0,
507         .protocol = ~0,
508       },
509     },
510     .type = PUNT_TYPE_L4,
511   };
512   /* *INDENT-ON* */
513
514   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
515     {
516       if (unformat (input, "ipv4"))
517         ;
518       else if (unformat (input, "ipv6"))
519         pr.punt.l4.af = AF_IP6;
520       else if (unformat (input, "udp"))
521         pr.punt.l4.protocol = IP_PROTOCOL_UDP;
522       else if (unformat (input, "tcp"))
523         pr.punt.l4.protocol = IP_PROTOCOL_TCP;
524       else if (unformat (input, "%d", &pr.punt.l4.port))
525         ;
526       else
527         {
528           error = clib_error_return (0, "parse error: '%U'",
529                                      format_unformat_error, input);
530           goto done;
531         }
532     }
533
534   error = vnet_punt_socket_del (vm, &pr);
535 done:
536   return error;
537 }
538
539 /*?
540  *
541  * @cliexpar
542  * @cliexcmd{punt socket register}
543  ?*/
544 /* *INDENT-OFF* */
545 VLIB_CLI_COMMAND (punt_socket_deregister_command, static) =
546 {
547   .path = "punt socket deregister",
548   .function = punt_socket_deregister_cmd,
549   .short_help = "punt socket deregister [ipv4|ipv6] [udp|tcp]> <all | port-num1 [port-num2 ...]>",
550   .is_mp_safe = 1,
551 };
552 /* *INDENT-ON* */
553
554 void
555 punt_client_walk (punt_type_t pt, punt_client_walk_cb_t cb, void *ctx)
556 {
557   punt_main_t *pm = &punt_main;
558
559   switch (pt)
560     {
561     case PUNT_TYPE_L4:
562       {
563         u32 pci;
564         u16 port;
565
566         /* *INDENT-OFF* */
567         hash_foreach(port, pci, pm->db.clients_by_l4_port,
568         ({
569           cb (pool_elt_at_index(pm->punt_client_pool, pci), ctx);
570         }));
571         /* *INDENT-ON* */
572         break;
573       }
574     case PUNT_TYPE_EXCEPTION:
575       {
576         u32 *pci;
577
578         vec_foreach (pci, pm->db.clients_by_exception)
579         {
580           if (~0 != *pci)
581             cb (pool_elt_at_index (pm->punt_client_pool, *pci), ctx);
582         }
583
584         break;
585       }
586     }
587 }
588
589 static u8 *
590 format_punt_client (u8 * s, va_list * args)
591 {
592   punt_client_t *pc = va_arg (*args, punt_client_t *);
593
594   s = format (s, " punt ");
595
596   switch (pc->reg.type)
597     {
598     case PUNT_TYPE_L4:
599       s = format (s, "%U %U port %d",
600                   format_ip_address_family, pc->reg.punt.l4.af,
601                   format_ip_protocol, pc->reg.punt.l4.protocol,
602                   pc->reg.punt.l4.port);
603       break;
604     case PUNT_TYPE_EXCEPTION:
605       s = format (s, " %U", format_vlib_punt_reason,
606                   pc->reg.punt.exception.reason);
607       break;
608     }
609
610   s = format (s, " to socket %s \n", pc->caddr.sun_path);
611
612   return (s);
613 }
614
615 static walk_rc_t
616 punt_client_show_one (const punt_client_t * pc, void *ctx)
617 {
618   vlib_cli_output (ctx, "%U", format_punt_client, pc);
619
620   return (WALK_CONTINUE);
621 }
622
623 static clib_error_t *
624 punt_socket_show_cmd (vlib_main_t * vm,
625                       unformat_input_t * input, vlib_cli_command_t * cmd)
626 {
627   clib_error_t *error = NULL;
628   punt_type_t pt;
629
630   pt = PUNT_TYPE_L4;
631
632   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
633     {
634       if (unformat (input, "exception"))
635         pt = PUNT_TYPE_EXCEPTION;
636       else if (unformat (input, "l4"))
637         pt = PUNT_TYPE_L4;
638       else
639         {
640           error = clib_error_return (0, "parse error: '%U'",
641                                      format_unformat_error, input);
642           goto done;
643         }
644     }
645
646   punt_client_walk (pt, punt_client_show_one, vm);
647
648 done:
649   return (error);
650 }
651
652 /*?
653  *
654  * @cliexpar
655  * @cliexcmd{show punt socket ipv4}
656  ?*/
657 /* *INDENT-OFF* */
658 VLIB_CLI_COMMAND (show_punt_socket_registration_command, static) =
659 {
660   .path = "show punt socket registrations",
661   .function = punt_socket_show_cmd,
662   .short_help = "show punt socket registrations [l4|exception]",
663   .is_mp_safe = 1,
664 };
665 /* *INDENT-ON* */
666
667 clib_error_t *
668 ip_punt_init (vlib_main_t * vm)
669 {
670   clib_error_t *error = NULL;
671   punt_main_t *pm = &punt_main;
672
673   pm->is_configured = false;
674   pm->interface_output_node =
675     vlib_get_node_by_name (vm, (u8 *) "interface-output");
676
677   if ((error = vlib_call_init_function (vm, punt_init)))
678     return error;
679
680   pm->hdl = vlib_punt_client_register ("ip-punt");
681
682   return (error);
683 }
684
685 VLIB_INIT_FUNCTION (ip_punt_init);
686
687 static clib_error_t *
688 punt_config (vlib_main_t * vm, unformat_input_t * input)
689 {
690   punt_main_t *pm = &punt_main;
691   char *socket_path = 0;
692
693   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
694     {
695       if (unformat (input, "socket %s", &socket_path))
696         strncpy (pm->sun_path, socket_path, UNIX_PATH_MAX - 1);
697       else
698         return clib_error_return (0, "unknown input `%U'",
699                                   format_unformat_error, input);
700     }
701
702   if (socket_path == 0)
703     return 0;
704
705   /* UNIX domain socket */
706   struct sockaddr_un addr;
707   if ((pm->socket_fd = socket (AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0)) == -1)
708     {
709       return clib_error_return (0, "socket error");
710     }
711
712   clib_memset (&addr, 0, sizeof (addr));
713   addr.sun_family = AF_UNIX;
714   if (*socket_path == '\0')
715     {
716       *addr.sun_path = '\0';
717       strncpy (addr.sun_path + 1, socket_path + 1,
718                sizeof (addr.sun_path) - 2);
719     }
720   else
721     {
722       strncpy (addr.sun_path, socket_path, sizeof (addr.sun_path) - 1);
723       unlink (socket_path);
724     }
725
726   if (bind (pm->socket_fd, (struct sockaddr *) &addr, sizeof (addr)) == -1)
727     {
728       return clib_error_return (0, "bind error");
729     }
730
731   int n_bytes = 0x10000;
732
733   if (setsockopt
734       (pm->socket_fd, SOL_SOCKET, SO_SNDBUF, &n_bytes,
735        sizeof (n_bytes)) == -1)
736     {
737       return clib_error_return (0, "setsockopt error");
738     }
739
740   /* Register socket */
741   clib_file_main_t *fm = &file_main;
742   clib_file_t template = { 0 };
743   template.read_function = punt_socket_read_ready;
744   template.file_descriptor = pm->socket_fd;
745   template.description = format (0, "%s", socket_path);
746   pm->clib_file_index = clib_file_add (fm, &template);
747
748   pm->is_configured = true;
749
750   return 0;
751 }
752
753 VLIB_CONFIG_FUNCTION (punt_config, "punt");
754
755 /*
756  * fd.io coding-style-patch-verification: ON
757  *
758  * Local Variables:
759  * eval: (c-set-style "gnu")
760  * End:
761  */