Annotation of src/sys/netinet/udp_usrreq.c, Revision 1.19
1.19 ! mycroft 1: /* $NetBSD: udp_usrreq.c,v 1.18 1995/06/12 00:48:06 mycroft Exp $ */
1.14 cgd 2:
1.1 cgd 3: /*
1.13 mycroft 4: * Copyright (c) 1982, 1986, 1988, 1990, 1993
5: * The Regents of the University of California. All rights reserved.
1.1 cgd 6: *
7: * Redistribution and use in source and binary forms, with or without
8: * modification, are permitted provided that the following conditions
9: * are met:
10: * 1. Redistributions of source code must retain the above copyright
11: * notice, this list of conditions and the following disclaimer.
12: * 2. Redistributions in binary form must reproduce the above copyright
13: * notice, this list of conditions and the following disclaimer in the
14: * documentation and/or other materials provided with the distribution.
15: * 3. All advertising materials mentioning features or use of this software
16: * must display the following acknowledgement:
17: * This product includes software developed by the University of
18: * California, Berkeley and its contributors.
19: * 4. Neither the name of the University nor the names of its contributors
20: * may be used to endorse or promote products derived from this software
21: * without specific prior written permission.
22: *
23: * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24: * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26: * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29: * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31: * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32: * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33: * SUCH DAMAGE.
34: *
1.14 cgd 35: * @(#)udp_usrreq.c 8.4 (Berkeley) 1/21/94
1.1 cgd 36: */
37:
1.5 mycroft 38: #include <sys/param.h>
39: #include <sys/malloc.h>
40: #include <sys/mbuf.h>
41: #include <sys/protosw.h>
42: #include <sys/socket.h>
43: #include <sys/socketvar.h>
1.13 mycroft 44: #include <sys/errno.h>
1.5 mycroft 45: #include <sys/stat.h>
1.1 cgd 46:
1.5 mycroft 47: #include <net/if.h>
48: #include <net/route.h>
1.1 cgd 49:
1.5 mycroft 50: #include <netinet/in.h>
51: #include <netinet/in_systm.h>
1.15 cgd 52: #include <netinet/in_var.h>
1.5 mycroft 53: #include <netinet/ip.h>
54: #include <netinet/in_pcb.h>
55: #include <netinet/ip_var.h>
56: #include <netinet/ip_icmp.h>
57: #include <netinet/udp.h>
58: #include <netinet/udp_var.h>
1.1 cgd 59:
1.8 mycroft 60: /*
61: * UDP protocol implementation.
62: * Per RFC 768, August, 1980.
63: */
64: #ifndef COMPAT_42
65: int udpcksum = 1;
66: #else
67: int udpcksum = 0; /* XXX */
68: #endif
69:
70: struct sockaddr_in udp_in = { sizeof(udp_in), AF_INET };
1.18 mycroft 71: struct inpcb *udp_last_inpcb = 0;
1.1 cgd 72:
1.13 mycroft 73: static void udp_detach __P((struct inpcb *));
74: static void udp_notify __P((struct inpcb *, int));
75: static struct mbuf *udp_saveopt __P((caddr_t, int, int));
1.7 mycroft 76:
77: void
1.1 cgd 78: udp_init()
79: {
1.18 mycroft 80:
81: in_pcbinit(&udbtable);
1.1 cgd 82: }
83:
1.7 mycroft 84: void
1.1 cgd 85: udp_input(m, iphlen)
86: register struct mbuf *m;
87: int iphlen;
88: {
89: register struct ip *ip;
90: register struct udphdr *uh;
91: register struct inpcb *inp;
92: struct mbuf *opts = 0;
93: int len;
94: struct ip save_ip;
95:
96: udpstat.udps_ipackets++;
97:
98: /*
99: * Strip IP options, if any; should skip this,
100: * make available to user, and use on returned packets,
101: * but we don't yet have a way to check the checksum
102: * with options still present.
103: */
104: if (iphlen > sizeof (struct ip)) {
105: ip_stripoptions(m, (struct mbuf *)0);
106: iphlen = sizeof(struct ip);
107: }
108:
109: /*
110: * Get IP and UDP header together in first mbuf.
111: */
112: ip = mtod(m, struct ip *);
113: if (m->m_len < iphlen + sizeof(struct udphdr)) {
114: if ((m = m_pullup(m, iphlen + sizeof(struct udphdr))) == 0) {
115: udpstat.udps_hdrops++;
116: return;
117: }
118: ip = mtod(m, struct ip *);
119: }
120: uh = (struct udphdr *)((caddr_t)ip + iphlen);
121:
122: /*
123: * Make mbuf data length reflect UDP length.
124: * If not enough data to reflect UDP length, drop.
125: */
1.15 cgd 126: len = ntohs((u_int16_t)uh->uh_ulen);
1.1 cgd 127: if (ip->ip_len != len) {
128: if (len > ip->ip_len) {
129: udpstat.udps_badlen++;
130: goto bad;
131: }
132: m_adj(m, len - ip->ip_len);
133: /* ip->ip_len = len; */
134: }
135: /*
136: * Save a copy of the IP header in case we want restore it
137: * for sending an ICMP error message in response.
138: */
139: save_ip = *ip;
140:
141: /*
142: * Checksum extended UDP header and data.
143: */
144: if (udpcksum && uh->uh_sum) {
145: ((struct ipovly *)ip)->ih_next = 0;
146: ((struct ipovly *)ip)->ih_prev = 0;
147: ((struct ipovly *)ip)->ih_x1 = 0;
148: ((struct ipovly *)ip)->ih_len = uh->uh_ulen;
149: if (uh->uh_sum = in_cksum(m, len + sizeof (struct ip))) {
150: udpstat.udps_badsum++;
151: m_freem(m);
152: return;
153: }
154: }
1.13 mycroft 155:
1.16 mycroft 156: if (IN_MULTICAST(ip->ip_dst.s_addr) ||
1.13 mycroft 157: in_broadcast(ip->ip_dst, m->m_pkthdr.rcvif)) {
1.4 hpeyerl 158: struct socket *last;
159: /*
160: * Deliver a multicast or broadcast datagram to *all* sockets
161: * for which the local and remote addresses and ports match
162: * those of the incoming datagram. This allows more than
163: * one process to receive multi/broadcasts on the same port.
164: * (This really ought to be done for unicast datagrams as
165: * well, but that would cause problems with existing
166: * applications that open both address-specific sockets and
167: * a wildcard socket listening to the same port -- they would
168: * end up receiving duplicates of every unicast datagram.
169: * Those applications open the multiple sockets to overcome an
170: * inadequacy of the UDP socket interface, but for backwards
171: * compatibility we avoid the problem here rather than
1.13 mycroft 172: * fixing the interface. Maybe 4.5BSD will remedy this?)
1.4 hpeyerl 173: */
1.13 mycroft 174:
1.4 hpeyerl 175: /*
176: * Construct sockaddr format source address.
177: */
178: udp_in.sin_port = uh->uh_sport;
179: udp_in.sin_addr = ip->ip_src;
180: m->m_len -= sizeof (struct udpiphdr);
181: m->m_data += sizeof (struct udpiphdr);
182: /*
183: * Locate pcb(s) for datagram.
184: * (Algorithm copied from raw_intr().)
185: */
186: last = NULL;
1.18 mycroft 187: for (inp = udbtable.inpt_list.lh_first; inp != 0;
188: inp = inp->inp_list.le_next) {
1.4 hpeyerl 189: if (inp->inp_lport != uh->uh_dport)
190: continue;
191: if (inp->inp_laddr.s_addr != INADDR_ANY) {
192: if (inp->inp_laddr.s_addr !=
193: ip->ip_dst.s_addr)
194: continue;
195: }
196: if (inp->inp_faddr.s_addr != INADDR_ANY) {
1.6 mycroft 197: if (inp->inp_faddr.s_addr !=
1.4 hpeyerl 198: ip->ip_src.s_addr ||
199: inp->inp_fport != uh->uh_sport)
200: continue;
201: }
202:
203: if (last != NULL) {
204: struct mbuf *n;
205:
206: if ((n = m_copy(m, 0, M_COPYALL)) != NULL) {
207: if (sbappendaddr(&last->so_rcv,
1.17 mycroft 208: sintosa(&udp_in), n,
209: (struct mbuf *)0) == 0) {
1.4 hpeyerl 210: m_freem(n);
1.13 mycroft 211: udpstat.udps_fullsock++;
212: } else
1.4 hpeyerl 213: sorwakeup(last);
214: }
215: }
216: last = inp->inp_socket;
217: /*
1.13 mycroft 218: * Don't look for additional matches if this one does
219: * not have either the SO_REUSEPORT or SO_REUSEADDR
220: * socket options set. This heuristic avoids searching
221: * through all pcbs in the common case of a non-shared
222: * port. It * assumes that an application will never
223: * clear these options after setting them.
1.4 hpeyerl 224: */
1.13 mycroft 225: if ((last->so_options&(SO_REUSEPORT|SO_REUSEADDR) == 0))
1.4 hpeyerl 226: break;
227: }
1.6 mycroft 228:
1.4 hpeyerl 229: if (last == NULL) {
230: /*
231: * No matching pcb found; discard datagram.
232: * (No need to send an ICMP Port Unreachable
233: * for a broadcast or multicast datgram.)
234: */
1.13 mycroft 235: udpstat.udps_noportbcast++;
1.4 hpeyerl 236: goto bad;
237: }
1.17 mycroft 238: if (sbappendaddr(&last->so_rcv, sintosa(&udp_in), m,
239: (struct mbuf *)0) == 0) {
1.13 mycroft 240: udpstat.udps_fullsock++;
1.4 hpeyerl 241: goto bad;
1.13 mycroft 242: }
1.4 hpeyerl 243: sorwakeup(last);
244: return;
245: }
1.1 cgd 246: /*
247: * Locate pcb for datagram.
248: */
249: inp = udp_last_inpcb;
1.18 mycroft 250: if (inp == 0 ||
251: inp->inp_lport != uh->uh_dport ||
1.1 cgd 252: inp->inp_fport != uh->uh_sport ||
253: inp->inp_faddr.s_addr != ip->ip_src.s_addr ||
254: inp->inp_laddr.s_addr != ip->ip_dst.s_addr) {
1.18 mycroft 255: udpstat.udpps_pcbcachemiss++;
256: inp = in_pcblookup(&udbtable, ip->ip_src, uh->uh_sport,
1.1 cgd 257: ip->ip_dst, uh->uh_dport, INPLOOKUP_WILDCARD);
1.18 mycroft 258: if (inp == 0) {
259: udpstat.udps_noport++;
260: if (m->m_flags & (M_BCAST | M_MCAST)) {
261: udpstat.udps_noportbcast++;
262: goto bad;
263: }
264: *ip = save_ip;
265: ip->ip_len += iphlen;
266: icmp_error(m, ICMP_UNREACH, ICMP_UNREACH_PORT, 0, 0);
267: return;
1.13 mycroft 268: }
1.18 mycroft 269: udp_last_inpcb = inp;
1.1 cgd 270: }
271:
272: /*
273: * Construct sockaddr format source address.
274: * Stuff source address and datagram in user buffer.
275: */
276: udp_in.sin_port = uh->uh_sport;
277: udp_in.sin_addr = ip->ip_src;
278: if (inp->inp_flags & INP_CONTROLOPTS) {
279: struct mbuf **mp = &opts;
280:
281: if (inp->inp_flags & INP_RECVDSTADDR) {
282: *mp = udp_saveopt((caddr_t) &ip->ip_dst,
283: sizeof(struct in_addr), IP_RECVDSTADDR);
284: if (*mp)
285: mp = &(*mp)->m_next;
286: }
287: #ifdef notyet
288: /* options were tossed above */
289: if (inp->inp_flags & INP_RECVOPTS) {
290: *mp = udp_saveopt((caddr_t) opts_deleted_above,
291: sizeof(struct in_addr), IP_RECVOPTS);
292: if (*mp)
293: mp = &(*mp)->m_next;
294: }
295: /* ip_srcroute doesn't do what we want here, need to fix */
296: if (inp->inp_flags & INP_RECVRETOPTS) {
297: *mp = udp_saveopt((caddr_t) ip_srcroute(),
298: sizeof(struct in_addr), IP_RECVRETOPTS);
299: if (*mp)
300: mp = &(*mp)->m_next;
301: }
302: #endif
303: }
304: iphlen += sizeof(struct udphdr);
305: m->m_len -= iphlen;
306: m->m_pkthdr.len -= iphlen;
307: m->m_data += iphlen;
1.17 mycroft 308: if (sbappendaddr(&inp->inp_socket->so_rcv, sintosa(&udp_in), m,
309: opts) == 0) {
1.1 cgd 310: udpstat.udps_fullsock++;
311: goto bad;
312: }
313: sorwakeup(inp->inp_socket);
314: return;
315: bad:
316: m_freem(m);
317: if (opts)
318: m_freem(opts);
319: }
320:
321: /*
322: * Create a "control" mbuf containing the specified data
323: * with the specified type for presentation with a datagram.
324: */
1.13 mycroft 325: struct mbuf *
1.1 cgd 326: udp_saveopt(p, size, type)
327: caddr_t p;
328: register int size;
329: int type;
330: {
331: register struct cmsghdr *cp;
332: struct mbuf *m;
333:
334: if ((m = m_get(M_DONTWAIT, MT_CONTROL)) == NULL)
335: return ((struct mbuf *) NULL);
336: cp = (struct cmsghdr *) mtod(m, struct cmsghdr *);
1.13 mycroft 337: bcopy(p, CMSG_DATA(cp), size);
1.1 cgd 338: size += sizeof(*cp);
339: m->m_len = size;
340: cp->cmsg_len = size;
341: cp->cmsg_level = IPPROTO_IP;
342: cp->cmsg_type = type;
343: return (m);
344: }
345:
346: /*
347: * Notify a udp user of an asynchronous error;
348: * just wake up so that he can collect error status.
349: */
1.7 mycroft 350: static void
1.1 cgd 351: udp_notify(inp, errno)
352: register struct inpcb *inp;
1.7 mycroft 353: int errno;
1.1 cgd 354: {
355: inp->inp_socket->so_error = errno;
356: sorwakeup(inp->inp_socket);
357: sowwakeup(inp->inp_socket);
358: }
359:
1.7 mycroft 360: void
1.1 cgd 361: udp_ctlinput(cmd, sa, ip)
362: int cmd;
363: struct sockaddr *sa;
364: register struct ip *ip;
365: {
366: register struct udphdr *uh;
367: extern struct in_addr zeroin_addr;
368: extern u_char inetctlerrmap[];
1.18 mycroft 369: void (*notify) __P((struct inpcb *, int)) = udp_notify;
1.1 cgd 370:
1.18 mycroft 371: if (PRC_IS_REDIRECT(cmd))
1.19 ! mycroft 372: notify = in_rtchange, ip = 0;
1.18 mycroft 373: else if (cmd == PRC_HOSTDEAD)
1.19 ! mycroft 374: ip = 0;
1.18 mycroft 375: else if ((unsigned)cmd >= PRC_NCMDS || inetctlerrmap[cmd] == 0)
1.1 cgd 376: return;
1.19 ! mycroft 377: if (ip) {
1.1 cgd 378: uh = (struct udphdr *)((caddr_t)ip + (ip->ip_hl << 2));
1.18 mycroft 379: in_pcbnotify(&udbtable, sa, uh->uh_dport, ip->ip_src,
380: uh->uh_sport, cmd, notify);
1.19 ! mycroft 381: } else
! 382: in_pcbnotifyall(&udbtable, sa, cmd, notify);
1.1 cgd 383: }
384:
1.7 mycroft 385: int
1.1 cgd 386: udp_output(inp, m, addr, control)
387: register struct inpcb *inp;
388: register struct mbuf *m;
389: struct mbuf *addr, *control;
390: {
391: register struct udpiphdr *ui;
392: register int len = m->m_pkthdr.len;
393: struct in_addr laddr;
394: int s, error = 0;
395:
396: if (control)
397: m_freem(control); /* XXX */
398:
399: if (addr) {
400: laddr = inp->inp_laddr;
401: if (inp->inp_faddr.s_addr != INADDR_ANY) {
402: error = EISCONN;
403: goto release;
404: }
405: /*
406: * Must block input while temporarily connected.
407: */
408: s = splnet();
409: error = in_pcbconnect(inp, addr);
410: if (error) {
411: splx(s);
412: goto release;
413: }
414: } else {
415: if (inp->inp_faddr.s_addr == INADDR_ANY) {
416: error = ENOTCONN;
417: goto release;
418: }
419: }
420: /*
421: * Calculate data length and get a mbuf
422: * for UDP and IP headers.
423: */
1.13 mycroft 424: M_PREPEND(m, sizeof(struct udpiphdr), M_DONTWAIT);
425: if (m == 0) {
426: error = ENOBUFS;
427: goto release;
428: }
1.1 cgd 429:
430: /*
431: * Fill in mbuf with extended UDP header
432: * and addresses and length put into network format.
433: */
434: ui = mtod(m, struct udpiphdr *);
435: ui->ui_next = ui->ui_prev = 0;
436: ui->ui_x1 = 0;
437: ui->ui_pr = IPPROTO_UDP;
1.15 cgd 438: ui->ui_len = htons((u_int16_t)len + sizeof (struct udphdr));
1.1 cgd 439: ui->ui_src = inp->inp_laddr;
440: ui->ui_dst = inp->inp_faddr;
441: ui->ui_sport = inp->inp_lport;
442: ui->ui_dport = inp->inp_fport;
443: ui->ui_ulen = ui->ui_len;
444:
445: /*
446: * Stuff checksum and output datagram.
447: */
448: ui->ui_sum = 0;
449: if (udpcksum) {
450: if ((ui->ui_sum = in_cksum(m, sizeof (struct udpiphdr) + len)) == 0)
451: ui->ui_sum = 0xffff;
452: }
453: ((struct ip *)ui)->ip_len = sizeof (struct udpiphdr) + len;
454: ((struct ip *)ui)->ip_ttl = inp->inp_ip.ip_ttl; /* XXX */
455: ((struct ip *)ui)->ip_tos = inp->inp_ip.ip_tos; /* XXX */
456: udpstat.udps_opackets++;
457: error = ip_output(m, inp->inp_options, &inp->inp_route,
1.12 mycroft 458: inp->inp_socket->so_options & (SO_DONTROUTE | SO_BROADCAST),
459: inp->inp_moptions);
1.1 cgd 460:
461: if (addr) {
462: in_pcbdisconnect(inp);
463: inp->inp_laddr = laddr;
464: splx(s);
465: }
466: return (error);
467:
468: release:
469: m_freem(m);
470: return (error);
471: }
472:
473: u_long udp_sendspace = 9216; /* really max datagram size */
474: u_long udp_recvspace = 40 * (1024 + sizeof(struct sockaddr_in));
475: /* 40 1K datagrams */
476:
477: /*ARGSUSED*/
1.7 mycroft 478: int
1.1 cgd 479: udp_usrreq(so, req, m, addr, control)
480: struct socket *so;
481: int req;
482: struct mbuf *m, *addr, *control;
483: {
484: struct inpcb *inp = sotoinpcb(so);
485: int error = 0;
486: int s;
487:
488: if (req == PRU_CONTROL)
1.15 cgd 489: return (in_control(so, (long)m, (caddr_t)addr,
1.1 cgd 490: (struct ifnet *)control));
491: if (inp == NULL && req != PRU_ATTACH) {
492: error = EINVAL;
493: goto release;
494: }
495: /*
496: * Note: need to block udp_input while changing
497: * the udp pcb queue and/or pcb addresses.
498: */
499: switch (req) {
500:
501: case PRU_ATTACH:
502: if (inp != NULL) {
503: error = EINVAL;
504: break;
505: }
506: s = splnet();
1.18 mycroft 507: error = in_pcballoc(so, &udbtable);
1.1 cgd 508: splx(s);
509: if (error)
510: break;
511: error = soreserve(so, udp_sendspace, udp_recvspace);
512: if (error)
513: break;
1.13 mycroft 514: ((struct inpcb *) so->so_pcb)->inp_ip.ip_ttl = ip_defttl;
1.1 cgd 515: break;
516:
517: case PRU_DETACH:
518: udp_detach(inp);
519: break;
520:
521: case PRU_BIND:
522: s = splnet();
523: error = in_pcbbind(inp, addr);
524: splx(s);
525: break;
526:
527: case PRU_LISTEN:
528: error = EOPNOTSUPP;
529: break;
530:
531: case PRU_CONNECT:
532: if (inp->inp_faddr.s_addr != INADDR_ANY) {
533: error = EISCONN;
534: break;
535: }
536: s = splnet();
537: error = in_pcbconnect(inp, addr);
538: splx(s);
539: if (error == 0)
540: soisconnected(so);
541: break;
542:
543: case PRU_CONNECT2:
544: error = EOPNOTSUPP;
545: break;
546:
547: case PRU_ACCEPT:
548: error = EOPNOTSUPP;
549: break;
550:
551: case PRU_DISCONNECT:
552: if (inp->inp_faddr.s_addr == INADDR_ANY) {
553: error = ENOTCONN;
554: break;
555: }
556: s = splnet();
557: in_pcbdisconnect(inp);
558: inp->inp_laddr.s_addr = INADDR_ANY;
559: splx(s);
560: so->so_state &= ~SS_ISCONNECTED; /* XXX */
561: break;
562:
563: case PRU_SHUTDOWN:
564: socantsendmore(so);
565: break;
566:
567: case PRU_SEND:
568: return (udp_output(inp, m, addr, control));
569:
570: case PRU_ABORT:
571: soisdisconnected(so);
572: udp_detach(inp);
573: break;
574:
575: case PRU_SOCKADDR:
576: in_setsockaddr(inp, addr);
577: break;
578:
579: case PRU_PEERADDR:
580: in_setpeeraddr(inp, addr);
581: break;
582:
583: case PRU_SENSE:
584: /*
585: * stat: don't bother with a blocksize.
586: */
587: return (0);
588:
589: case PRU_SENDOOB:
590: case PRU_FASTTIMO:
591: case PRU_SLOWTIMO:
592: case PRU_PROTORCV:
593: case PRU_PROTOSEND:
594: error = EOPNOTSUPP;
595: break;
596:
597: case PRU_RCVD:
598: case PRU_RCVOOB:
599: return (EOPNOTSUPP); /* do not free mbuf's */
600:
601: default:
602: panic("udp_usrreq");
603: }
604:
605: release:
606: if (control) {
607: printf("udp control data unexpectedly retained\n");
608: m_freem(control);
609: }
610: if (m)
611: m_freem(m);
612: return (error);
613: }
614:
1.7 mycroft 615: static void
1.1 cgd 616: udp_detach(inp)
617: struct inpcb *inp;
618: {
619: int s = splnet();
620:
621: if (inp == udp_last_inpcb)
1.18 mycroft 622: udp_last_inpcb = 0;
1.1 cgd 623: in_pcbdetach(inp);
624: splx(s);
1.13 mycroft 625: }
626:
627: /*
628: * Sysctl for udp variables.
629: */
630: udp_sysctl(name, namelen, oldp, oldlenp, newp, newlen)
631: int *name;
632: u_int namelen;
633: void *oldp;
634: size_t *oldlenp;
635: void *newp;
636: size_t newlen;
637: {
638: /* All sysctl names at this level are terminal. */
639: if (namelen != 1)
640: return (ENOTDIR);
641:
642: switch (name[0]) {
643: case UDPCTL_CHECKSUM:
644: return (sysctl_int(oldp, oldlenp, newp, newlen, &udpcksum));
645: default:
646: return (ENOPROTOOPT);
647: }
648: /* NOTREACHED */
1.1 cgd 649: }
CVSweb <webmaster@jp.NetBSD.org>