keysock.c revision 1.14 1 /* $NetBSD: keysock.c,v 1.14 2007/07/07 18:38:23 degroote Exp $ */
2 /* $FreeBSD: src/sys/netipsec/keysock.c,v 1.3.2.1 2003/01/24 05:11:36 sam Exp $ */
3 /* $KAME: keysock.c,v 1.25 2001/08/13 20:07:41 itojun Exp $ */
4
5 /*
6 * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 * 3. Neither the name of the project nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34 #include <sys/cdefs.h>
35 __KERNEL_RCSID(0, "$NetBSD: keysock.c,v 1.14 2007/07/07 18:38:23 degroote Exp $");
36
37 #include "opt_ipsec.h"
38
39 /* This code has derived from sys/net/rtsock.c on FreeBSD2.2.5 */
40
41 #include <sys/types.h>
42 #include <sys/param.h>
43 #include <sys/domain.h>
44 #include <sys/errno.h>
45 #include <sys/kernel.h>
46 #include <sys/malloc.h>
47 #include <sys/mbuf.h>
48 #include <sys/protosw.h>
49 #include <sys/signalvar.h>
50 #include <sys/socket.h>
51 #include <sys/socketvar.h>
52 #include <sys/sysctl.h>
53 #include <sys/systm.h>
54
55 #include <net/raw_cb.h>
56 #include <net/route.h>
57
58 #include <net/pfkeyv2.h>
59 #include <netipsec/key.h>
60 #include <netipsec/keysock.h>
61 #include <netipsec/key_debug.h>
62
63 #include <netipsec/ipsec_osdep.h>
64
65 #include <machine/stdarg.h>
66
67 typedef int pr_output_t (struct mbuf *, struct socket *);
68
69 struct key_cb {
70 int key_count;
71 int any_count;
72 };
73 static struct key_cb key_cb;
74
75 static struct sockaddr key_dst = {
76 .sa_len = 2,
77 .sa_family = PF_KEY,
78 };
79 static struct sockaddr key_src = {
80 .sa_len = 2,
81 .sa_family = PF_KEY,
82 };
83
84
85 static int key_sendup0 __P((struct rawcb *, struct mbuf *, int, int));
86
87 struct pfkeystat pfkeystat;
88
89 int key_registered_sb_max = (NMBCLUSTERS * MHLEN); /* XXX arbitrary */
90
91 /* XXX sysctl */
92 #ifdef __FreeBSD__
93 SYSCTL_INT(_net_key, OID_AUTO, registered_sbmax, CTLFLAG_RD,
94 &key_registered_sb_max , 0, "Maximum kernel-to-user PFKEY datagram size");
95 #endif
96
97 /*
98 * key_output()
99 */
100 int
101 key_output(struct mbuf *m, ...)
102 {
103 struct sadb_msg *msg;
104 int len, error = 0;
105 int s;
106 struct socket *so;
107 va_list ap;
108
109 va_start(ap, m);
110 so = va_arg(ap, struct socket *);
111 va_end(ap);
112
113 if (m == 0)
114 panic("key_output: NULL pointer was passed");
115
116 pfkeystat.out_total++;
117 pfkeystat.out_bytes += m->m_pkthdr.len;
118
119 len = m->m_pkthdr.len;
120 if (len < sizeof(struct sadb_msg)) {
121 pfkeystat.out_tooshort++;
122 error = EINVAL;
123 goto end;
124 }
125
126 if (m->m_len < sizeof(struct sadb_msg)) {
127 if ((m = m_pullup(m, sizeof(struct sadb_msg))) == 0) {
128 pfkeystat.out_nomem++;
129 error = ENOBUFS;
130 goto end;
131 }
132 }
133
134 if ((m->m_flags & M_PKTHDR) == 0)
135 panic("key_output: not M_PKTHDR ??");
136
137 KEYDEBUG(KEYDEBUG_KEY_DUMP, kdebug_mbuf(m));
138
139 msg = mtod(m, struct sadb_msg *);
140 pfkeystat.out_msgtype[msg->sadb_msg_type]++;
141 if (len != PFKEY_UNUNIT64(msg->sadb_msg_len)) {
142 pfkeystat.out_invlen++;
143 error = EINVAL;
144 goto end;
145 }
146
147 /*XXX giant lock*/
148 s = splsoftnet();
149 error = key_parse(m, so);
150 m = NULL;
151 splx(s);
152 end:
153 if (m)
154 m_freem(m);
155 return error;
156 }
157
158 /*
159 * send message to the socket.
160 */
161 static int
162 key_sendup0(
163 struct rawcb *rp,
164 struct mbuf *m,
165 int promisc,
166 int sbprio
167 )
168 {
169 int error;
170 int ok;
171
172 if (promisc) {
173 struct sadb_msg *pmsg;
174
175 M_PREPEND(m, sizeof(struct sadb_msg), M_DONTWAIT);
176 if (m && m->m_len < sizeof(struct sadb_msg))
177 m = m_pullup(m, sizeof(struct sadb_msg));
178 if (!m) {
179 pfkeystat.in_nomem++;
180 m_freem(m);
181 return ENOBUFS;
182 }
183 m->m_pkthdr.len += sizeof(*pmsg);
184
185 pmsg = mtod(m, struct sadb_msg *);
186 bzero(pmsg, sizeof(*pmsg));
187 pmsg->sadb_msg_version = PF_KEY_V2;
188 pmsg->sadb_msg_type = SADB_X_PROMISC;
189 pmsg->sadb_msg_len = PFKEY_UNIT64(m->m_pkthdr.len);
190 /* pid and seq? */
191
192 pfkeystat.in_msgtype[pmsg->sadb_msg_type]++;
193 }
194
195 if (sbprio == 0)
196 ok = sbappendaddr(&rp->rcb_socket->so_rcv,
197 (struct sockaddr *)&key_src, m, NULL);
198 else
199 ok = sbappendaddrchain(&rp->rcb_socket->so_rcv,
200 (struct sockaddr *)&key_src, m, sbprio);
201
202 if (!ok) {
203 pfkeystat.in_nomem++;
204 m_freem(m);
205 error = ENOBUFS;
206 } else
207 error = 0;
208 sorwakeup(rp->rcb_socket);
209 return error;
210 }
211
212 /* XXX this interface should be obsoleted. */
213 int
214 key_sendup(struct socket *so, struct sadb_msg *msg, u_int len,
215 int target) /*target of the resulting message*/
216 {
217 struct mbuf *m, *n, *mprev;
218 int tlen;
219
220 /* sanity check */
221 if (so == 0 || msg == 0)
222 panic("key_sendup: NULL pointer was passed");
223
224 KEYDEBUG(KEYDEBUG_KEY_DUMP,
225 printf("key_sendup: \n");
226 kdebug_sadb(msg));
227
228 /*
229 * we increment statistics here, just in case we have ENOBUFS
230 * in this function.
231 */
232 pfkeystat.in_total++;
233 pfkeystat.in_bytes += len;
234 pfkeystat.in_msgtype[msg->sadb_msg_type]++;
235
236 /*
237 * Get mbuf chain whenever possible (not clusters),
238 * to save socket buffer. We'll be generating many SADB_ACQUIRE
239 * messages to listening key sockets. If we simply allocate clusters,
240 * sbappendaddr() will raise ENOBUFS due to too little sbspace().
241 * sbspace() computes # of actual data bytes AND mbuf region.
242 *
243 * TODO: SADB_ACQUIRE filters should be implemented.
244 */
245 tlen = len;
246 m = mprev = NULL;
247 while (tlen > 0) {
248 if (tlen == len) {
249 MGETHDR(n, M_DONTWAIT, MT_DATA);
250 n->m_len = MHLEN;
251 } else {
252 MGET(n, M_DONTWAIT, MT_DATA);
253 n->m_len = MLEN;
254 }
255 if (!n) {
256 pfkeystat.in_nomem++;
257 return ENOBUFS;
258 }
259 if (tlen >= MCLBYTES) { /*XXX better threshold? */
260 MCLGET(n, M_DONTWAIT);
261 if ((n->m_flags & M_EXT) == 0) {
262 m_free(n);
263 m_freem(m);
264 pfkeystat.in_nomem++;
265 return ENOBUFS;
266 }
267 n->m_len = MCLBYTES;
268 }
269
270 if (tlen < n->m_len)
271 n->m_len = tlen;
272 n->m_next = NULL;
273 if (m == NULL)
274 m = mprev = n;
275 else {
276 mprev->m_next = n;
277 mprev = n;
278 }
279 tlen -= n->m_len;
280 n = NULL;
281 }
282 m->m_pkthdr.len = len;
283 m->m_pkthdr.rcvif = NULL;
284 m_copyback(m, 0, len, msg);
285
286 /* avoid duplicated statistics */
287 pfkeystat.in_total--;
288 pfkeystat.in_bytes -= len;
289 pfkeystat.in_msgtype[msg->sadb_msg_type]--;
290
291 return key_sendup_mbuf(so, m, target);
292 }
293
294 /* so can be NULL if target != KEY_SENDUP_ONE */
295 int
296 key_sendup_mbuf(struct socket *so, struct mbuf *m,
297 int target/*, sbprio */)
298 {
299 struct mbuf *n;
300 struct keycb *kp;
301 int sendup;
302 struct rawcb *rp;
303 int error = 0;
304 int sbprio = 0; /* XXX should be a parameter */
305
306 if (m == NULL)
307 panic("key_sendup_mbuf: NULL pointer was passed");
308 if (so == NULL && target == KEY_SENDUP_ONE)
309 panic("key_sendup_mbuf: NULL pointer was passed");
310
311 /*
312 * RFC 2367 says ACQUIRE and other kernel-generated messages
313 * are special. We treat all KEY_SENDUP_REGISTERED messages
314 * as special, delivering them to all registered sockets
315 * even if the socket is at or above its so->so_rcv.sb_max limits.
316 * The only constraint is that the so_rcv data fall below
317 * key_registered_sb_max.
318 * Doing that check here avoids reworking every key_sendup_mbuf()
319 * in the short term. . The rework will be done after a technical
320 * conensus that this approach is appropriate.
321 */
322 if (target == KEY_SENDUP_REGISTERED) {
323 sbprio = SB_PRIO_BESTEFFORT;
324 }
325
326 pfkeystat.in_total++;
327 pfkeystat.in_bytes += m->m_pkthdr.len;
328 if (m->m_len < sizeof(struct sadb_msg)) {
329 #if 1
330 m = m_pullup(m, sizeof(struct sadb_msg));
331 if (m == NULL) {
332 pfkeystat.in_nomem++;
333 return ENOBUFS;
334 }
335 #else
336 /* don't bother pulling it up just for stats */
337 #endif
338 }
339 if (m->m_len >= sizeof(struct sadb_msg)) {
340 struct sadb_msg *msg;
341 msg = mtod(m, struct sadb_msg *);
342 pfkeystat.in_msgtype[msg->sadb_msg_type]++;
343 }
344
345 LIST_FOREACH(rp, &rawcb_list, rcb_list)
346 {
347 struct socket * kso = rp->rcb_socket;
348 if (rp->rcb_proto.sp_family != PF_KEY)
349 continue;
350 if (rp->rcb_proto.sp_protocol
351 && rp->rcb_proto.sp_protocol != PF_KEY_V2) {
352 continue;
353 }
354
355 kp = (struct keycb *)rp;
356
357 /*
358 * If you are in promiscuous mode, and when you get broadcasted
359 * reply, you'll get two PF_KEY messages.
360 * (based on pf_key (at) inner.net message on 14 Oct 1998)
361 */
362 if (((struct keycb *)rp)->kp_promisc) {
363 if ((n = m_copy(m, 0, (int)M_COPYALL)) != NULL) {
364 (void)key_sendup0(rp, n, 1, 0);
365 n = NULL;
366 }
367 }
368
369 /* the exact target will be processed later */
370 if (so && sotorawcb(so) == rp)
371 continue;
372
373 sendup = 0;
374 switch (target) {
375 case KEY_SENDUP_ONE:
376 /* the statement has no effect */
377 if (so && sotorawcb(so) == rp)
378 sendup++;
379 break;
380 case KEY_SENDUP_ALL:
381 sendup++;
382 break;
383 case KEY_SENDUP_REGISTERED:
384 if (kp->kp_registered) {
385 if (kso->so_rcv.sb_cc <= key_registered_sb_max)
386 sendup++;
387 else
388 printf("keysock: "
389 "registered sendup dropped, "
390 "sb_cc %ld max %d\n",
391 kso->so_rcv.sb_cc,
392 key_registered_sb_max);
393 }
394 break;
395 }
396 pfkeystat.in_msgtarget[target]++;
397
398 if (!sendup)
399 continue;
400
401 if ((n = m_copy(m, 0, (int)M_COPYALL)) == NULL) {
402 m_freem(m);
403 pfkeystat.in_nomem++;
404 return ENOBUFS;
405 }
406
407 if ((error = key_sendup0(rp, n, 0, 0)) != 0) {
408 m_freem(m);
409 return error;
410 }
411
412 n = NULL;
413 }
414
415 /* The 'later' time for processing the exact target has arrived */
416 if (so) {
417 error = key_sendup0(sotorawcb(so), m, 0, sbprio);
418 m = NULL;
419 } else {
420 error = 0;
421 m_freem(m);
422 }
423 return error;
424 }
425
426 #ifdef __FreeBSD__
427
428 /*
429 * key_abort()
430 * derived from net/rtsock.c:rts_abort()
431 */
432 static int
433 key_abort(struct socket *so)
434 {
435 int s, error;
436 s = splnet(); /* FreeBSD */
437 error = raw_usrreqs.pru_abort(so);
438 splx(s);
439 return error;
440 }
441
442 /*
443 * key_attach()
444 * derived from net/rtsock.c:rts_attach()
445 */
446 static int
447 key_attach(struct socket *so, int proto, struct proc *td)
448 {
449 struct keycb *kp;
450 int s, error;
451
452 if (sotorawcb(so) != 0)
453 return EISCONN; /* XXX panic? */
454 kp = (struct keycb *)malloc(sizeof *kp, M_PCB, M_WAITOK|M_ZERO); /* XXX */
455 if (kp == 0)
456 return ENOBUFS;
457
458 /*
459 * The spl[soft]net() is necessary to block protocols from sending
460 * error notifications (like RTM_REDIRECT or RTM_LOSING) while
461 * this PCB is extant but incompletely initialized.
462 * Probably we should try to do more of this work beforehand and
463 * eliminate the spl.
464 */
465 s = splnet(); /* FreeBSD */
466 so->so_pcb = kp;
467 error = raw_usrreqs.pru_attach(so, proto, td);
468 kp = (struct keycb *)sotorawcb(so);
469 if (error) {
470 free(kp, M_PCB);
471 so->so_pcb = NULL;
472 splx(s);
473 return error;
474 }
475
476 kp->kp_promisc = kp->kp_registered = 0;
477
478 if (kp->kp_raw.rcb_proto.sp_protocol == PF_KEY) /* XXX: AF_KEY */
479 key_cb.key_count++;
480 key_cb.any_count++;
481 kp->kp_raw.rcb_laddr = &key_src;
482 kp->kp_raw.rcb_faddr = &key_dst;
483 soisconnected(so);
484 so->so_options |= SO_USELOOPBACK;
485
486 splx(s);
487 return 0;
488 }
489
490 /*
491 * key_bind()
492 * derived from net/rtsock.c:rts_bind()
493 */
494 static int
495 key_bind(struct socket *so, struct sockaddr *nam, struct proc *td)
496 {
497 int s, error;
498 s = splnet(); /* FreeBSD */
499 error = raw_usrreqs.pru_bind(so, nam, td); /* xxx just EINVAL */
500 splx(s);
501 return error;
502 }
503
504 /*
505 * key_connect()
506 * derived from net/rtsock.c:rts_connect()
507 */
508 static int
509 key_connect(struct socket *so, struct sockaddr *nam, struct proc *td)
510 {
511 int s, error;
512 s = splnet(); /* FreeBSD */
513 error = raw_usrreqs.pru_connect(so, nam, td); /* XXX just EINVAL */
514 splx(s);
515 return error;
516 }
517
518 /*
519 * key_detach()
520 * derived from net/rtsock.c:rts_detach()
521 */
522 static int
523 key_detach(struct socket *so)
524 {
525 struct keycb *kp = (struct keycb *)sotorawcb(so);
526 int s, error;
527
528 s = splnet(); /* FreeBSD */
529 if (kp != 0) {
530 if (kp->kp_raw.rcb_proto.sp_protocol
531 == PF_KEY) /* XXX: AF_KEY */
532 key_cb.key_count--;
533 key_cb.any_count--;
534
535 key_freereg(so);
536 }
537 error = raw_usrreqs.pru_detach(so);
538 splx(s);
539 return error;
540 }
541
542 /*
543 * key_disconnect()
544 * derived from net/rtsock.c:key_disconnect()
545 */
546 static int
547 key_disconnect(struct socket *so)
548 {
549 int s, error;
550 s = splnet(); /* FreeBSD */
551 error = raw_usrreqs.pru_disconnect(so);
552 splx(s);
553 return error;
554 }
555
556 /*
557 * key_peeraddr()
558 * derived from net/rtsock.c:rts_peeraddr()
559 */
560 static int
561 key_peeraddr(struct socket *so, struct sockaddr **nam)
562 {
563 int s, error;
564 s = splnet(); /* FreeBSD */
565 error = raw_usrreqs.pru_peeraddr(so, nam);
566 splx(s);
567 return error;
568 }
569
570 /*
571 * key_send()
572 * derived from net/rtsock.c:rts_send()
573 */
574 static int
575 key_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *nam,
576 struct mbuf *control, struct proc *td)
577 {
578 int s, error;
579 s = splnet(); /* FreeBSD */
580 error = raw_usrreqs.pru_send(so, flags, m, nam, control, td);
581 splx(s);
582 return error;
583 }
584
585 /*
586 * key_shutdown()
587 * derived from net/rtsock.c:rts_shutdown()
588 */
589 static int
590 key_shutdown(struct socket *so)
591 {
592 int s, error;
593 s = splnet(); /* FreeBSD */
594 error = raw_usrreqs.pru_shutdown(so);
595 splx(s);
596 return error;
597 }
598
599 /*
600 * key_sockaddr()
601 * derived from net/rtsock.c:rts_sockaddr()
602 */
603 static int
604 key_sockaddr(struct socket *so, struct sockaddr **nam)
605 {
606 int s, error;
607 s = splnet(); /* FreeBSD */
608 error = raw_usrreqs.pru_sockaddr(so, nam);
609 splx(s);
610 return error;
611 }
612 #else /*!__FreeBSD__ -- traditional proto_usrreq() switch */
613
614 /*
615 * key_usrreq()
616 * derived from net/rtsock.c:route_usrreq()
617 */
618 int
619 key_usrreq(struct socket *so, int req,struct mbuf *m, struct mbuf *nam,
620 struct mbuf *control, struct lwp *l)
621 {
622 int error = 0;
623 struct keycb *kp = (struct keycb *)sotorawcb(so);
624 int s;
625
626 s = splsoftnet();
627 if (req == PRU_ATTACH) {
628 kp = (struct keycb *)malloc(sizeof(*kp), M_PCB, M_WAITOK);
629 so->so_pcb = kp;
630 if (so->so_pcb)
631 bzero(so->so_pcb, sizeof(*kp));
632 }
633 if (req == PRU_DETACH && kp) {
634 int af = kp->kp_raw.rcb_proto.sp_protocol;
635 if (af == PF_KEY) /* XXX: AF_KEY */
636 key_cb.key_count--;
637 key_cb.any_count--;
638
639 key_freereg(so);
640 }
641
642 error = raw_usrreq(so, req, m, nam, control, l);
643 m = control = NULL; /* reclaimed in raw_usrreq */
644 kp = (struct keycb *)sotorawcb(so);
645 if (req == PRU_ATTACH && kp) {
646 int af = kp->kp_raw.rcb_proto.sp_protocol;
647 if (error) {
648 pfkeystat.sockerr++;
649 free(kp, M_PCB);
650 so->so_pcb = NULL;
651 splx(s);
652 return (error);
653 }
654
655 kp->kp_promisc = kp->kp_registered = 0;
656
657 if (af == PF_KEY) /* XXX: AF_KEY */
658 key_cb.key_count++;
659 key_cb.any_count++;
660 kp->kp_raw.rcb_laddr = &key_src;
661 kp->kp_raw.rcb_faddr = &key_dst;
662 soisconnected(so);
663 so->so_options |= SO_USELOOPBACK;
664 }
665 splx(s);
666 return (error);
667 }
668 #endif /*!__FreeBSD__*/
669
670 /* sysctl */
671 #ifdef SYSCTL_NODE
672 SYSCTL_NODE(_net, PF_KEY, key, CTLFLAG_RW, 0, "Key Family");
673 #endif /* SYSCTL_NODE */
674
675 /*
676 * Definitions of protocols supported in the KEY domain.
677 */
678
679 #ifdef __FreeBSD__
680 extern struct domain keydomain;
681
682 struct pr_usrreqs key_usrreqs = {
683 key_abort, pru_accept_notsupp, key_attach, key_bind,
684 key_connect,
685 pru_connect2_notsupp, pru_control_notsupp, key_detach,
686 key_disconnect, pru_listen_notsupp, key_peeraddr,
687 pru_rcvd_notsupp,
688 pru_rcvoob_notsupp, key_send, pru_sense_null, key_shutdown,
689 key_sockaddr, sosend, soreceive, sopoll
690 };
691
692 struct protosw keysw[] = {
693 { SOCK_RAW, &keydomain, PF_KEY_V2, PR_ATOMIC|PR_ADDR,
694 0, (pr_output_t *)key_output, raw_ctlinput, 0,
695 0,
696 raw_init, 0, 0, 0,
697 &key_usrreqs
698 }
699 };
700
701 static void
702 key_init0(void)
703 {
704 bzero(&key_cb, sizeof(key_cb));
705 key_init();
706 }
707
708 struct domain keydomain =
709 { PF_KEY, "key", key_init0, 0, 0,
710 keysw, &keysw[sizeof(keysw)/sizeof(keysw[0])] };
711
712 DOMAIN_SET(key);
713
714 #else /* !__FreeBSD__ */
715
716 DOMAIN_DEFINE(keydomain);
717
718 const struct protosw keysw[] = {
719 {
720 .pr_type = SOCK_RAW,
721 .pr_domain = &keydomain,
722 .pr_protocol = PF_KEY_V2,
723 .pr_flags = PR_ATOMIC|PR_ADDR,
724 .pr_output = key_output,
725 .pr_ctlinput = raw_ctlinput,
726 .pr_usrreq = key_usrreq,
727 .pr_init = raw_init,
728 }
729 };
730
731 struct domain keydomain = {
732 .dom_family = PF_KEY,
733 .dom_name = "key",
734 .dom_init = key_init,
735 .dom_protosw = keysw,
736 .dom_protoswNPROTOSW = &keysw[__arraycount(keysw)],
737 };
738
739 #endif
740