Annotation of src/sys/netinet/udp_usrreq.c, Revision 1.18
1.18 ! mycroft 1: /* $NetBSD: udp_usrreq.c,v 1.17 1995/06/04 05:07:20 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))
! 372: notify = in_rtchange;
! 373: else if (cmd == PRC_HOSTDEAD)
! 374: ;
! 375: else if ((unsigned)cmd >= PRC_NCMDS || inetctlerrmap[cmd] == 0)
1.1 cgd 376: return;
1.18 ! mycroft 377: else 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);
! 381: return;
! 382: }
! 383: in_pcbnotifyall(&udbtable, sa, cmd, notify);
1.1 cgd 384: }
385:
1.7 mycroft 386: int
1.1 cgd 387: udp_output(inp, m, addr, control)
388: register struct inpcb *inp;
389: register struct mbuf *m;
390: struct mbuf *addr, *control;
391: {
392: register struct udpiphdr *ui;
393: register int len = m->m_pkthdr.len;
394: struct in_addr laddr;
395: int s, error = 0;
396:
397: if (control)
398: m_freem(control); /* XXX */
399:
400: if (addr) {
401: laddr = inp->inp_laddr;
402: if (inp->inp_faddr.s_addr != INADDR_ANY) {
403: error = EISCONN;
404: goto release;
405: }
406: /*
407: * Must block input while temporarily connected.
408: */
409: s = splnet();
410: error = in_pcbconnect(inp, addr);
411: if (error) {
412: splx(s);
413: goto release;
414: }
415: } else {
416: if (inp->inp_faddr.s_addr == INADDR_ANY) {
417: error = ENOTCONN;
418: goto release;
419: }
420: }
421: /*
422: * Calculate data length and get a mbuf
423: * for UDP and IP headers.
424: */
1.13 mycroft 425: M_PREPEND(m, sizeof(struct udpiphdr), M_DONTWAIT);
426: if (m == 0) {
427: error = ENOBUFS;
428: goto release;
429: }
1.1 cgd 430:
431: /*
432: * Fill in mbuf with extended UDP header
433: * and addresses and length put into network format.
434: */
435: ui = mtod(m, struct udpiphdr *);
436: ui->ui_next = ui->ui_prev = 0;
437: ui->ui_x1 = 0;
438: ui->ui_pr = IPPROTO_UDP;
1.15 cgd 439: ui->ui_len = htons((u_int16_t)len + sizeof (struct udphdr));
1.1 cgd 440: ui->ui_src = inp->inp_laddr;
441: ui->ui_dst = inp->inp_faddr;
442: ui->ui_sport = inp->inp_lport;
443: ui->ui_dport = inp->inp_fport;
444: ui->ui_ulen = ui->ui_len;
445:
446: /*
447: * Stuff checksum and output datagram.
448: */
449: ui->ui_sum = 0;
450: if (udpcksum) {
451: if ((ui->ui_sum = in_cksum(m, sizeof (struct udpiphdr) + len)) == 0)
452: ui->ui_sum = 0xffff;
453: }
454: ((struct ip *)ui)->ip_len = sizeof (struct udpiphdr) + len;
455: ((struct ip *)ui)->ip_ttl = inp->inp_ip.ip_ttl; /* XXX */
456: ((struct ip *)ui)->ip_tos = inp->inp_ip.ip_tos; /* XXX */
457: udpstat.udps_opackets++;
458: error = ip_output(m, inp->inp_options, &inp->inp_route,
1.12 mycroft 459: inp->inp_socket->so_options & (SO_DONTROUTE | SO_BROADCAST),
460: inp->inp_moptions);
1.1 cgd 461:
462: if (addr) {
463: in_pcbdisconnect(inp);
464: inp->inp_laddr = laddr;
465: splx(s);
466: }
467: return (error);
468:
469: release:
470: m_freem(m);
471: return (error);
472: }
473:
474: u_long udp_sendspace = 9216; /* really max datagram size */
475: u_long udp_recvspace = 40 * (1024 + sizeof(struct sockaddr_in));
476: /* 40 1K datagrams */
477:
478: /*ARGSUSED*/
1.7 mycroft 479: int
1.1 cgd 480: udp_usrreq(so, req, m, addr, control)
481: struct socket *so;
482: int req;
483: struct mbuf *m, *addr, *control;
484: {
485: struct inpcb *inp = sotoinpcb(so);
486: int error = 0;
487: int s;
488:
489: if (req == PRU_CONTROL)
1.15 cgd 490: return (in_control(so, (long)m, (caddr_t)addr,
1.1 cgd 491: (struct ifnet *)control));
492: if (inp == NULL && req != PRU_ATTACH) {
493: error = EINVAL;
494: goto release;
495: }
496: /*
497: * Note: need to block udp_input while changing
498: * the udp pcb queue and/or pcb addresses.
499: */
500: switch (req) {
501:
502: case PRU_ATTACH:
503: if (inp != NULL) {
504: error = EINVAL;
505: break;
506: }
507: s = splnet();
1.18 ! mycroft 508: error = in_pcballoc(so, &udbtable);
1.1 cgd 509: splx(s);
510: if (error)
511: break;
512: error = soreserve(so, udp_sendspace, udp_recvspace);
513: if (error)
514: break;
1.13 mycroft 515: ((struct inpcb *) so->so_pcb)->inp_ip.ip_ttl = ip_defttl;
1.1 cgd 516: break;
517:
518: case PRU_DETACH:
519: udp_detach(inp);
520: break;
521:
522: case PRU_BIND:
523: s = splnet();
524: error = in_pcbbind(inp, addr);
525: splx(s);
526: break;
527:
528: case PRU_LISTEN:
529: error = EOPNOTSUPP;
530: break;
531:
532: case PRU_CONNECT:
533: if (inp->inp_faddr.s_addr != INADDR_ANY) {
534: error = EISCONN;
535: break;
536: }
537: s = splnet();
538: error = in_pcbconnect(inp, addr);
539: splx(s);
540: if (error == 0)
541: soisconnected(so);
542: break;
543:
544: case PRU_CONNECT2:
545: error = EOPNOTSUPP;
546: break;
547:
548: case PRU_ACCEPT:
549: error = EOPNOTSUPP;
550: break;
551:
552: case PRU_DISCONNECT:
553: if (inp->inp_faddr.s_addr == INADDR_ANY) {
554: error = ENOTCONN;
555: break;
556: }
557: s = splnet();
558: in_pcbdisconnect(inp);
559: inp->inp_laddr.s_addr = INADDR_ANY;
560: splx(s);
561: so->so_state &= ~SS_ISCONNECTED; /* XXX */
562: break;
563:
564: case PRU_SHUTDOWN:
565: socantsendmore(so);
566: break;
567:
568: case PRU_SEND:
569: return (udp_output(inp, m, addr, control));
570:
571: case PRU_ABORT:
572: soisdisconnected(so);
573: udp_detach(inp);
574: break;
575:
576: case PRU_SOCKADDR:
577: in_setsockaddr(inp, addr);
578: break;
579:
580: case PRU_PEERADDR:
581: in_setpeeraddr(inp, addr);
582: break;
583:
584: case PRU_SENSE:
585: /*
586: * stat: don't bother with a blocksize.
587: */
588: return (0);
589:
590: case PRU_SENDOOB:
591: case PRU_FASTTIMO:
592: case PRU_SLOWTIMO:
593: case PRU_PROTORCV:
594: case PRU_PROTOSEND:
595: error = EOPNOTSUPP;
596: break;
597:
598: case PRU_RCVD:
599: case PRU_RCVOOB:
600: return (EOPNOTSUPP); /* do not free mbuf's */
601:
602: default:
603: panic("udp_usrreq");
604: }
605:
606: release:
607: if (control) {
608: printf("udp control data unexpectedly retained\n");
609: m_freem(control);
610: }
611: if (m)
612: m_freem(m);
613: return (error);
614: }
615:
1.7 mycroft 616: static void
1.1 cgd 617: udp_detach(inp)
618: struct inpcb *inp;
619: {
620: int s = splnet();
621:
622: if (inp == udp_last_inpcb)
1.18 ! mycroft 623: udp_last_inpcb = 0;
1.1 cgd 624: in_pcbdetach(inp);
625: splx(s);
1.13 mycroft 626: }
627:
628: /*
629: * Sysctl for udp variables.
630: */
631: udp_sysctl(name, namelen, oldp, oldlenp, newp, newlen)
632: int *name;
633: u_int namelen;
634: void *oldp;
635: size_t *oldlenp;
636: void *newp;
637: size_t newlen;
638: {
639: /* All sysctl names at this level are terminal. */
640: if (namelen != 1)
641: return (ENOTDIR);
642:
643: switch (name[0]) {
644: case UDPCTL_CHECKSUM:
645: return (sysctl_int(oldp, oldlenp, newp, newlen, &udpcksum));
646: default:
647: return (ENOPROTOOPT);
648: }
649: /* NOTREACHED */
1.1 cgd 650: }
CVSweb <webmaster@jp.NetBSD.org>