Annotation of embedaddon/mtr/ui/net.c, revision 1.1.1.1
1.1 misho 1: /*
2: mtr -- a network diagnostic tool
3: Copyright (C) 1997,1998 Matt Kimball
4:
5: This program is free software; you can redistribute it and/or modify
6: it under the terms of the GNU General Public License version 2 as
7: published by the Free Software Foundation.
8:
9: This program is distributed in the hope that it will be useful,
10: but WITHOUT ANY WARRANTY; without even the implied warranty of
11: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12: GNU General Public License for more details.
13:
14: You should have received a copy of the GNU General Public License
15: along with this program; if not, write to the Free Software
16: Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17: */
18:
19: #include "config.h"
20:
21: #include <errno.h>
22: #include <math.h>
23: #include <stdlib.h>
24: #include <string.h>
25: #include <sys/select.h>
26: #include <unistd.h>
27:
28: #ifdef HAVE_ERROR_H
29: #include <error.h>
30: #else
31: #include "portability/error.h"
32: #endif
33:
34: #include "mtr.h"
35: #include "cmdpipe.h"
36: #include "net.h"
37: #include "display.h"
38: #include "dns.h"
39: #include "utils.h"
40:
41: #define MinSequence 33000
42: #define MaxSequence 65536
43:
44: static int packetsize; /* packet size used by ping */
45:
46: static void sockaddrtop(
47: struct sockaddr *saddr,
48: char *strptr,
49: size_t len);
50:
51: struct nethost {
52: ip_t addr;
53: ip_t addrs[MAXPATH]; /* for multi paths byMin */
54: int xmit;
55: int returned;
56: int sent;
57: int up;
58: long long ssd; /* sum of squares of differences from the current average */
59: int last;
60: int best;
61: int worst;
62: int avg; /* average: addByMin */
63: int gmean; /* geometric mean: addByMin */
64: int jitter; /* current jitter, defined as t1-t0 addByMin */
65: int javg; /* avg jitter */
66: int jworst; /* max jitter */
67: int jinta; /* estimated variance,? rfc1889's "Interarrival Jitter" */
68: int transit;
69: int saved[SAVED_PINGS];
70: int saved_seq_offset;
71: struct mplslen mpls;
72: struct mplslen mplss[MAXPATH];
73: };
74:
75:
76: struct sequence {
77: int index;
78: int transit;
79: int saved_seq;
80: struct timeval time;
81: };
82:
83:
84: static struct nethost host[MaxHost];
85: static struct sequence sequence[MaxSequence];
86: static struct packet_command_pipe_t packet_command_pipe;
87:
88: #ifdef ENABLE_IPV6
89: static struct sockaddr_storage sourcesockaddr_struct;
90: static struct sockaddr_storage remotesockaddr_struct;
91: static struct sockaddr_in6 *ssa6 =
92: (struct sockaddr_in6 *) &sourcesockaddr_struct;
93: static struct sockaddr_in6 *rsa6 =
94: (struct sockaddr_in6 *) &remotesockaddr_struct;
95: #else
96: static struct sockaddr_in sourcesockaddr_struct;
97: static struct sockaddr_in remotesockaddr_struct;
98: #endif
99:
100: static struct sockaddr *sourcesockaddr =
101: (struct sockaddr *) &sourcesockaddr_struct;
102: static struct sockaddr *remotesockaddr =
103: (struct sockaddr *) &remotesockaddr_struct;
104: static struct sockaddr_in *ssa4 =
105: (struct sockaddr_in *) &sourcesockaddr_struct;
106: static struct sockaddr_in *rsa4 =
107: (struct sockaddr_in *) &remotesockaddr_struct;
108:
109: static ip_t *sourceaddress;
110: static ip_t *remoteaddress;
111:
112: #ifdef ENABLE_IPV6
113: static char localaddr[INET6_ADDRSTRLEN];
114: #else
115: #ifndef INET_ADDRSTRLEN
116: #define INET_ADDRSTRLEN 16
117: #endif
118: static char localaddr[INET_ADDRSTRLEN];
119: #endif
120:
121: static int batch_at = 0;
122: static int numhosts = 10;
123:
124: /* return the number of microseconds to wait before sending the next
125: ping */
126: int calc_deltatime(
127: float waittime)
128: {
129: waittime /= numhosts;
130: return 1000000 * waittime;
131: }
132:
133:
134: static void save_sequence(
135: struct mtr_ctl *ctl,
136: int index,
137: int seq)
138: {
139: display_rawxmit(ctl, index, seq);
140:
141: sequence[seq].index = index;
142: sequence[seq].transit = 1;
143: sequence[seq].saved_seq = ++host[index].xmit;
144: memset(&sequence[seq].time, 0, sizeof(sequence[seq].time));
145:
146: host[index].transit = 1;
147:
148: if (host[index].sent) {
149: host[index].up = 0;
150: }
151:
152: host[index].sent = 1;
153: net_save_xmit(index);
154: }
155:
156: static int new_sequence(
157: struct mtr_ctl *ctl,
158: int index)
159: {
160: static int next_sequence = MinSequence;
161: int seq;
162:
163: seq = next_sequence++;
164: if (next_sequence >= MaxSequence) {
165: next_sequence = MinSequence;
166: }
167:
168: save_sequence(ctl, index, seq);
169:
170: return seq;
171: }
172:
173:
174: /* Attempt to find the host at a particular number of hops away */
175: static void net_send_query(
176: struct mtr_ctl *ctl,
177: int index,
178: int packet_size)
179: {
180: int seq = new_sequence(ctl, index);
181: int time_to_live = index + 1;
182:
183: send_probe_command(ctl, &packet_command_pipe, remoteaddress,
184: sourceaddress, packetsize, seq, time_to_live);
185: }
186:
187:
188: /* We got a return on something we sent out. Record the address and
189: time. */
190: static void net_process_ping(
191: struct mtr_ctl *ctl,
192: int seq,
193: struct mplslen *mpls,
194: ip_t * addr,
195: int totusec)
196: {
197: int index;
198: int oldavg; /* usedByMin */
199: int oldjavg; /* usedByMin */
200: int i; /* usedByMin */
201: #ifdef ENABLE_IPV6
202: char addrcopy[sizeof(struct in6_addr)];
203: #else
204: char addrcopy[sizeof(struct in_addr)];
205: #endif
206:
207: addrcpy((void *) &addrcopy, (char *) addr, ctl->af);
208:
209: if ((seq < 0) || (seq >= MaxSequence)) {
210: return;
211: }
212:
213: if (!sequence[seq].transit) {
214: return;
215: }
216: sequence[seq].transit = 0;
217:
218: index = sequence[seq].index;
219:
220: if (addrcmp((void *) &(host[index].addr),
221: (void *) &ctl->unspec_addr, ctl->af) == 0) {
222: /* should be out of if as addr can change */
223: addrcpy((void *) &(host[index].addr), addrcopy, ctl->af);
224: host[index].mpls = *mpls;
225: display_rawhost(ctl, index, (void *) &(host[index].addr));
226:
227: /* multi paths */
228: addrcpy((void *) &(host[index].addrs[0]), addrcopy, ctl->af);
229: host[index].mplss[0] = *mpls;
230: } else {
231: for (i = 0; i < MAXPATH;) {
232: if (addrcmp
233: ((void *) &(host[index].addrs[i]), (void *) &addrcopy,
234: ctl->af) == 0
235: || addrcmp((void *) &(host[index].addrs[i]),
236: (void *) &ctl->unspec_addr, ctl->af) == 0) {
237: break;
238: }
239: i++;
240: }
241:
242: if (addrcmp((void *) &(host[index].addrs[i]), addrcopy, ctl->af) !=
243: 0 && i < MAXPATH) {
244: addrcpy((void *) &(host[index].addrs[i]), addrcopy, ctl->af);
245: host[index].mplss[i] = *mpls;
246: display_rawhost(ctl, index, (void *) &(host[index].addrs[i]));
247: }
248: }
249:
250: host[index].jitter = totusec - host[index].last;
251: if (host[index].jitter < 0) {
252: host[index].jitter = -host[index].jitter;
253: }
254:
255: host[index].last = totusec;
256:
257: if (host[index].returned < 1) {
258: host[index].best = host[index].worst = host[index].gmean = totusec;
259: host[index].avg = host[index].ssd = 0;
260:
261: host[index].jitter = host[index].jworst = host[index].jinta = 0;
262: }
263:
264: if (totusec < host[index].best) {
265: host[index].best = totusec;
266: }
267: if (totusec > host[index].worst) {
268: host[index].worst = totusec;
269: }
270:
271: if (host[index].jitter > host[index].jworst) {
272: host[index].jworst = host[index].jitter;
273: }
274:
275: host[index].returned++;
276: oldavg = host[index].avg;
277: host[index].avg += (totusec - oldavg + .0) / host[index].returned;
278: host[index].ssd +=
279: (totusec - oldavg + .0) * (totusec - host[index].avg);
280:
281: oldjavg = host[index].javg;
282: host[index].javg +=
283: (host[index].jitter - oldjavg) / host[index].returned;
284: /* below algorithm is from rfc1889, A.8 */
285: host[index].jinta +=
286: host[index].jitter - ((host[index].jinta + 8) >> 4);
287:
288: if (host[index].returned > 1) {
289: host[index].gmean =
290: pow((double) host[index].gmean,
291: (host[index].returned - 1.0) / host[index].returned)
292: * pow((double) totusec, 1.0 / host[index].returned);
293: }
294:
295: host[index].sent = 0;
296: host[index].up = 1;
297: host[index].transit = 0;
298:
299: net_save_return(index, sequence[seq].saved_seq, totusec);
300: display_rawping(ctl, index, totusec, seq);
301: }
302:
303: /*
304: Invoked when the read pipe from the mtr-packet subprocess is readable.
305: If we have received a complete reply, process it.
306: */
307: void net_process_return(
308: struct mtr_ctl *ctl)
309: {
310: handle_command_replies(ctl, &packet_command_pipe, net_process_ping);
311: }
312:
313:
314: ip_t *net_addr(
315: int at)
316: {
317: return (ip_t *) & (host[at].addr);
318: }
319:
320:
321: ip_t *net_addrs(
322: int at,
323: int i)
324: {
325: return (ip_t *) & (host[at].addrs[i]);
326: }
327:
328: void *net_mpls(
329: int at)
330: {
331: return (struct mplslen *) &(host[at].mplss);
332: }
333:
334: void *net_mplss(
335: int at,
336: int i)
337: {
338: return (struct mplslen *) &(host[at].mplss[i]);
339: }
340:
341: int net_loss(
342: int at)
343: {
344: if ((host[at].xmit - host[at].transit) == 0) {
345: return 0;
346: }
347:
348: /* times extra 1000 */
349: return 1000 * (100 -
350: (100.0 * host[at].returned /
351: (host[at].xmit - host[at].transit)));
352: }
353:
354:
355: int net_drop(
356: int at)
357: {
358: return (host[at].xmit - host[at].transit) - host[at].returned;
359: }
360:
361:
362: int net_last(
363: int at)
364: {
365: return (host[at].last);
366: }
367:
368:
369: int net_best(
370: int at)
371: {
372: return (host[at].best);
373: }
374:
375:
376: int net_worst(
377: int at)
378: {
379: return (host[at].worst);
380: }
381:
382:
383: int net_avg(
384: int at)
385: {
386: return (host[at].avg);
387: }
388:
389:
390: int net_gmean(
391: int at)
392: {
393: return (host[at].gmean);
394: }
395:
396:
397: int net_stdev(
398: int at)
399: {
400: if (host[at].returned > 1) {
401: return (sqrt(host[at].ssd / (host[at].returned - 1.0)));
402: } else {
403: return (0);
404: }
405: }
406:
407:
408: int net_jitter(
409: int at)
410: {
411: return (host[at].jitter);
412: }
413:
414:
415: int net_jworst(
416: int at)
417: {
418: return (host[at].jworst);
419: }
420:
421:
422: int net_javg(
423: int at)
424: {
425: return (host[at].javg);
426: }
427:
428:
429: int net_jinta(
430: int at)
431: {
432: return (host[at].jinta);
433: }
434:
435:
436: int net_max(
437: struct mtr_ctl *ctl)
438: {
439: int at;
440: int max;
441:
442: max = 0;
443: for (at = 0; at < ctl->maxTTL - 1; at++) {
444: if (addrcmp((void *) &(host[at].addr),
445: (void *) remoteaddress, ctl->af) == 0) {
446: return at + 1;
447: } else if (addrcmp((void *) &(host[at].addr),
448: (void *) &ctl->unspec_addr, ctl->af) != 0) {
449: max = at + 2;
450: }
451: }
452:
453: return max;
454: }
455:
456:
457: int net_min(
458: struct mtr_ctl *ctl)
459: {
460: return (ctl->fstTTL - 1);
461: }
462:
463:
464: int net_returned(
465: int at)
466: {
467: return host[at].returned;
468: }
469:
470:
471: int net_xmit(
472: int at)
473: {
474: return host[at].xmit;
475: }
476:
477:
478: int net_up(
479: int at)
480: {
481: return host[at].up;
482: }
483:
484:
485: char *net_localaddr(
486: void)
487: {
488: return localaddr;
489: }
490:
491:
492: void net_end_transit(
493: void)
494: {
495: int at;
496:
497: for (at = 0; at < MaxHost; at++) {
498: host[at].transit = 0;
499: }
500: }
501:
502: int net_send_batch(
503: struct mtr_ctl *ctl)
504: {
505: int n_unknown = 0, i;
506:
507: /* randomized packet size and/or bit pattern if packetsize<0 and/or
508: bitpattern<0. abs(packetsize) and/or abs(bitpattern) will be used
509: */
510: if (batch_at < ctl->fstTTL) {
511: if (ctl->cpacketsize < 0) {
512: /* Someone used a formula here that tried to correct for the
513: "end-error" in "rand()". By "end-error" I mean that if you
514: have a range for "rand()" that runs to 32768, and the
515: destination range is 10000, you end up with 4 out of 32768
516: 0-2768's and only 3 out of 32768 for results 2769 .. 9999.
517: As our detination range (in the example 10000) is much
518: smaller (reasonable packet sizes), and our rand() range much
519: larger, this effect is insignificant. Oh! That other formula
520: didn't work. */
521: packetsize =
522: MINPACKET + rand() % (-ctl->cpacketsize - MINPACKET);
523: } else {
524: packetsize = ctl->cpacketsize;
525: }
526: if (ctl->bitpattern < 0) {
527: ctl->bitpattern =
528: -(int) (256 + 255 * (rand() / (RAND_MAX + 0.1)));
529: }
530: }
531:
532: net_send_query(ctl, batch_at, abs(packetsize));
533:
534: for (i = ctl->fstTTL - 1; i < batch_at; i++) {
535: if (addrcmp
536: ((void *) &(host[i].addr), (void *) &ctl->unspec_addr,
537: ctl->af) == 0)
538: n_unknown++;
539:
540: /* The second condition in the next "if" statement was added in mtr-0.56,
541: but I don't remember why. It makes mtr stop skipping sections of unknown
542: hosts. Removed in 0.65.
543: If the line proves necessary, it should at least NOT trigger that line
544: when host[i].addr == 0 */
545: if ((addrcmp((void *) &(host[i].addr),
546: (void *) remoteaddress, ctl->af) == 0))
547: n_unknown = MaxHost; /* Make sure we drop into "we should restart" */
548: }
549:
550: if ( /* success in reaching target */
551: (addrcmp((void *) &(host[batch_at].addr),
552: (void *) remoteaddress, ctl->af) == 0) ||
553: /* fail in consecutive maxUnknown (firewall?) */
554: (n_unknown > ctl->maxUnknown) ||
555: /* or reach limit */
556: (batch_at >= ctl->maxTTL - 1)) {
557: numhosts = batch_at + 1;
558: batch_at = ctl->fstTTL - 1;
559: return 1;
560: }
561:
562: batch_at++;
563: return 0;
564: }
565:
566:
567: /* Ensure the interface address a valid address for our use */
568: static void net_validate_interface_address(
569: int address_family,
570: char *interface_address)
571: {
572: if (inet_pton(address_family, interface_address, sourceaddress) != 1) {
573: error(EXIT_FAILURE, errno, "invalid local address");
574: }
575:
576: if (inet_ntop
577: (address_family, sourceaddress, localaddr,
578: sizeof(localaddr)) == NULL) {
579: error(EXIT_FAILURE, errno, "invalid local address");
580: }
581: }
582:
583:
584: /*
585: Find the local address we will use to sent to the remote
586: host by connecting a UDP socket and checking the address
587: the socket is bound to.
588: */
589: static void net_find_local_address(
590: void)
591: {
592: int udp_socket;
593: int addr_length;
594: struct sockaddr_storage remote_sockaddr;
595: struct sockaddr_in *remote4;
596: struct sockaddr_in6 *remote6;
597:
598: udp_socket =
599: socket(remotesockaddr->sa_family, SOCK_DGRAM, IPPROTO_UDP);
600: if (udp_socket == -1) {
601: error(EXIT_FAILURE, errno, "udp socket creation failed");
602: }
603:
604: /*
605: We need to set the port to a non-zero value for the connect
606: to succeed.
607: */
608: if (remotesockaddr->sa_family == AF_INET6) {
609: #ifdef ENABLE_IPV6
610: addr_length = sizeof(struct sockaddr_in6);
611:
612: memcpy(&remote_sockaddr, rsa6, addr_length);
613: remote6 = (struct sockaddr_in6 *) &remote_sockaddr;
614: remote6->sin6_port = htons(1);
615: #endif
616: } else {
617: addr_length = sizeof(struct sockaddr_in);
618:
619: memcpy(&remote_sockaddr, rsa4, addr_length);
620: remote4 = (struct sockaddr_in *) &remote_sockaddr;
621: remote4->sin_port = htons(1);
622: }
623:
624: if (connect
625: (udp_socket, (struct sockaddr *) &remote_sockaddr, addr_length)) {
626: error(EXIT_FAILURE, errno, "udp socket connect failed");
627: }
628:
629: if (getsockname(udp_socket, sourcesockaddr, &addr_length)) {
630:
631: error(EXIT_FAILURE, errno, "local address determination failed");
632: }
633:
634: sockaddrtop(sourcesockaddr, localaddr, sizeof(localaddr));
635:
636: close(udp_socket);
637: }
638:
639:
640: int net_open(
641: struct mtr_ctl *ctl,
642: struct hostent *hostent)
643: {
644: int err;
645:
646: /* Spawn the mtr-packet child process */
647: err = open_command_pipe(ctl, &packet_command_pipe);
648: if (err) {
649: return err;
650: }
651:
652: net_reset(ctl);
653:
654: remotesockaddr->sa_family = hostent->h_addrtype;
655:
656: switch (hostent->h_addrtype) {
657: case AF_INET:
658: addrcpy((void *) &(rsa4->sin_addr), hostent->h_addr, AF_INET);
659: sourceaddress = (ip_t *) & (ssa4->sin_addr);
660: remoteaddress = (ip_t *) & (rsa4->sin_addr);
661: break;
662: #ifdef ENABLE_IPV6
663: case AF_INET6:
664: addrcpy((void *) &(rsa6->sin6_addr), hostent->h_addr, AF_INET6);
665: sourceaddress = (ip_t *) & (ssa6->sin6_addr);
666: remoteaddress = (ip_t *) & (rsa6->sin6_addr);
667: break;
668: #endif
669: default:
670: error(EXIT_FAILURE, 0, "net_open bad address type");
671: }
672:
673: if (ctl->InterfaceAddress) {
674: net_validate_interface_address(ctl->af, ctl->InterfaceAddress);
675: } else {
676: net_find_local_address();
677: }
678:
679: return 0;
680: }
681:
682:
683: void net_reopen(
684: struct mtr_ctl *ctl,
685: struct hostent *addr)
686: {
687: int at;
688:
689: for (at = 0; at < MaxHost; at++) {
690: memset(&host[at], 0, sizeof(host[at]));
691: }
692:
693: remotesockaddr->sa_family = addr->h_addrtype;
694: addrcpy((void *) remoteaddress, addr->h_addr, addr->h_addrtype);
695:
696: switch (addr->h_addrtype) {
697: case AF_INET:
698: addrcpy((void *) &(rsa4->sin_addr), addr->h_addr, AF_INET);
699: break;
700: #ifdef ENABLE_IPV6
701: case AF_INET6:
702: addrcpy((void *) &(rsa6->sin6_addr), addr->h_addr, AF_INET6);
703: break;
704: #endif
705: default:
706: error(EXIT_FAILURE, 0, "net_reopen bad address type");
707: }
708:
709: net_reset(ctl);
710: net_send_batch(ctl);
711: }
712:
713:
714: void net_reset(
715: struct mtr_ctl *ctl)
716: {
717: static struct nethost template = {
718: .saved_seq_offset = 2 - SAVED_PINGS
719: };
720:
721: int at, i;
722:
723: batch_at = ctl->fstTTL - 1; /* above replacedByMin */
724: numhosts = 10;
725:
726: for (i = 0; i < SAVED_PINGS; i++)
727: template.saved[i] = -2;
728:
729: for (at = 0; at < MaxHost; at++) {
730: memcpy(&(host[at]), &template, sizeof(template));
731: }
732:
733: for (at = 0; at < MaxSequence; at++) {
734: sequence[at].transit = 0;
735: }
736:
737: }
738:
739:
740: /* Close the pipe to the packet generator process, and kill the process */
741: void net_close(
742: void)
743: {
744: close_command_pipe(&packet_command_pipe);
745: }
746:
747:
748: int net_waitfd(
749: void)
750: {
751: return packet_command_pipe.read_fd;
752: }
753:
754:
755: int *net_saved_pings(
756: int at)
757: {
758: return host[at].saved;
759: }
760:
761:
762: static void net_save_increment(
763: void)
764: {
765: int at;
766: for (at = 0; at < MaxHost; at++) {
767: memmove(host[at].saved, host[at].saved + 1,
768: (SAVED_PINGS - 1) * sizeof(int));
769: host[at].saved[SAVED_PINGS - 1] = -2;
770: host[at].saved_seq_offset += 1;
771: }
772: }
773:
774:
775: void net_save_xmit(
776: int at)
777: {
778: if (host[at].saved[SAVED_PINGS - 1] != -2)
779: net_save_increment();
780: host[at].saved[SAVED_PINGS - 1] = -1;
781: }
782:
783:
784: void net_save_return(
785: int at,
786: int seq,
787: int ms)
788: {
789: int idx;
790: idx = seq - host[at].saved_seq_offset;
791: if ((idx < 0) || (idx >= SAVED_PINGS)) {
792: return;
793: }
794: host[at].saved[idx] = ms;
795: }
796:
797: /* Similar to inet_ntop but uses a sockaddr as it's argument. */
798: static void sockaddrtop(
799: struct sockaddr *saddr,
800: char *strptr,
801: size_t len)
802: {
803: struct sockaddr_in *sa4;
804: #ifdef ENABLE_IPV6
805: struct sockaddr_in6 *sa6;
806: #endif
807:
808: switch (saddr->sa_family) {
809: case AF_INET:
810: sa4 = (struct sockaddr_in *) saddr;
811: xstrncpy(strptr, inet_ntoa(sa4->sin_addr), len - 1);
812: strptr[len - 1] = '\0';
813: return;
814: #ifdef ENABLE_IPV6
815: case AF_INET6:
816: sa6 = (struct sockaddr_in6 *) saddr;
817: inet_ntop(sa6->sin6_family, &(sa6->sin6_addr), strptr, len);
818: return;
819: #endif
820: default:
821: error(0, 0, "sockaddrtop unknown address type");
822: strptr[0] = '\0';
823: return;
824: }
825: }
826:
827:
828: /* Address comparison. */
829: int addrcmp(
830: char *a,
831: char *b,
832: int family)
833: {
834: int rc = -1;
835:
836: switch (family) {
837: case AF_INET:
838: rc = memcmp(a, b, sizeof(struct in_addr));
839: break;
840: #ifdef ENABLE_IPV6
841: case AF_INET6:
842: rc = memcmp(a, b, sizeof(struct in6_addr));
843: break;
844: #endif
845: }
846:
847: return rc;
848: }
849:
850: /* Address copy. */
851: void addrcpy(
852: char *a,
853: char *b,
854: int family)
855: {
856:
857: switch (family) {
858: case AF_INET:
859: memcpy(a, b, sizeof(struct in_addr));
860: break;
861: #ifdef ENABLE_IPV6
862: case AF_INET6:
863: memcpy(a, b, sizeof(struct in6_addr));
864: break;
865: #endif
866: }
867: }
868:
869: /* for GTK frontend */
870: void net_harvest_fds(
871: struct mtr_ctl *ctl)
872: {
873: fd_set writefd;
874: int maxfd = 0;
875: struct timeval tv;
876:
877: FD_ZERO(&writefd);
878: tv.tv_sec = 0;
879: tv.tv_usec = 0;
880: select(maxfd, NULL, &writefd, NULL, &tv);
881: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>