|
|
1.1 root 1: #include "slirp.h"
2:
3: /* host address */
4: struct in_addr our_addr;
5: /* host dns address */
6: struct in_addr dns_addr;
7: /* host loopback address */
8: struct in_addr loopback_addr;
9:
10: /* address for slirp virtual addresses */
11: struct in_addr special_addr;
1.1.1.2 root 12: /* virtual address alias for host */
13: struct in_addr alias_addr;
1.1 root 14:
1.1.1.4 ! root 15: static const uint8_t special_ethaddr[6] = {
1.1 root 16: 0x52, 0x54, 0x00, 0x12, 0x35, 0x00
17: };
18:
19: uint8_t client_ethaddr[6];
20:
21: int do_slowtimo;
22: int link_up;
23: struct timeval tt;
24: FILE *lfd;
25: struct ex_list *exec_list;
26:
27: /* XXX: suppress those select globals */
28: fd_set *global_readfds, *global_writefds, *global_xfds;
29:
1.1.1.2 root 30: char slirp_hostname[33];
31:
1.1 root 32: #ifdef _WIN32
33:
34: static int get_dns_addr(struct in_addr *pdns_addr)
35: {
36: FIXED_INFO *FixedInfo=NULL;
37: ULONG BufLen;
38: DWORD ret;
39: IP_ADDR_STRING *pIPAddr;
40: struct in_addr tmp_addr;
1.1.1.4 ! root 41:
1.1 root 42: FixedInfo = (FIXED_INFO *)GlobalAlloc(GPTR, sizeof(FIXED_INFO));
43: BufLen = sizeof(FIXED_INFO);
1.1.1.4 ! root 44:
1.1 root 45: if (ERROR_BUFFER_OVERFLOW == GetNetworkParams(FixedInfo, &BufLen)) {
46: if (FixedInfo) {
47: GlobalFree(FixedInfo);
48: FixedInfo = NULL;
49: }
50: FixedInfo = GlobalAlloc(GPTR, BufLen);
51: }
1.1.1.4 ! root 52:
1.1 root 53: if ((ret = GetNetworkParams(FixedInfo, &BufLen)) != ERROR_SUCCESS) {
54: printf("GetNetworkParams failed. ret = %08x\n", (u_int)ret );
55: if (FixedInfo) {
56: GlobalFree(FixedInfo);
57: FixedInfo = NULL;
58: }
59: return -1;
60: }
1.1.1.4 ! root 61:
1.1 root 62: pIPAddr = &(FixedInfo->DnsServerList);
63: inet_aton(pIPAddr->IpAddress.String, &tmp_addr);
64: *pdns_addr = tmp_addr;
65: #if 0
66: printf( "DNS Servers:\n" );
67: printf( "DNS Addr:%s\n", pIPAddr->IpAddress.String );
1.1.1.4 ! root 68:
1.1 root 69: pIPAddr = FixedInfo -> DnsServerList.Next;
70: while ( pIPAddr ) {
71: printf( "DNS Addr:%s\n", pIPAddr ->IpAddress.String );
72: pIPAddr = pIPAddr ->Next;
73: }
74: #endif
75: if (FixedInfo) {
76: GlobalFree(FixedInfo);
77: FixedInfo = NULL;
78: }
79: return 0;
80: }
81:
82: #else
83:
84: static int get_dns_addr(struct in_addr *pdns_addr)
85: {
86: char buff[512];
87: char buff2[256];
88: FILE *f;
89: int found = 0;
90: struct in_addr tmp_addr;
1.1.1.4 ! root 91:
1.1 root 92: f = fopen("/etc/resolv.conf", "r");
93: if (!f)
94: return -1;
95:
1.1.1.4 ! root 96: #ifdef DEBUG
1.1 root 97: lprint("IP address of your DNS(s): ");
1.1.1.4 ! root 98: #endif
1.1 root 99: while (fgets(buff, 512, f) != NULL) {
100: if (sscanf(buff, "nameserver%*[ \t]%256s", buff2) == 1) {
101: if (!inet_aton(buff2, &tmp_addr))
102: continue;
103: if (tmp_addr.s_addr == loopback_addr.s_addr)
104: tmp_addr = our_addr;
105: /* If it's the first one, set it to dns_addr */
106: if (!found)
107: *pdns_addr = tmp_addr;
1.1.1.4 ! root 108: #ifdef DEBUG
1.1 root 109: else
110: lprint(", ");
1.1.1.4 ! root 111: #endif
1.1 root 112: if (++found > 3) {
1.1.1.4 ! root 113: #ifdef DEBUG
1.1 root 114: lprint("(more)");
1.1.1.4 ! root 115: #endif
1.1 root 116: break;
1.1.1.4 ! root 117: }
! 118: #ifdef DEBUG
! 119: else
1.1 root 120: lprint("%s", inet_ntoa(tmp_addr));
1.1.1.4 ! root 121: #endif
1.1 root 122: }
123: }
124: fclose(f);
125: if (!found)
126: return -1;
127: return 0;
128: }
129:
130: #endif
131:
132: #ifdef _WIN32
1.1.1.4 ! root 133: static void slirp_cleanup(void)
1.1 root 134: {
135: WSACleanup();
136: }
137: #endif
138:
139: void slirp_init(void)
140: {
141: // debug_init("/tmp/slirp.log", DEBUG_DEFAULT);
1.1.1.4 ! root 142:
1.1 root 143: #ifdef _WIN32
144: {
145: WSADATA Data;
146: WSAStartup(MAKEWORD(2,0), &Data);
147: atexit(slirp_cleanup);
148: }
149: #endif
150:
151: link_up = 1;
152:
153: if_init();
154: ip_init();
155:
156: /* Initialise mbufs *after* setting the MTU */
157: m_init();
158:
159: /* set default addresses */
160: inet_aton("127.0.0.1", &loopback_addr);
161:
162: if (get_dns_addr(&dns_addr) < 0) {
1.1.1.2 root 163: dns_addr = loopback_addr;
164: fprintf (stderr, "Warning: No DNS servers found\n");
1.1 root 165: }
166:
167: inet_aton(CTL_SPECIAL, &special_addr);
1.1.1.2 root 168: alias_addr.s_addr = special_addr.s_addr | htonl(CTL_ALIAS);
169: getouraddr();
1.1 root 170: }
171:
172: #define CONN_CANFSEND(so) (((so)->so_state & (SS_FCANTSENDMORE|SS_ISFCONNECTED)) == SS_ISFCONNECTED)
173: #define CONN_CANFRCV(so) (((so)->so_state & (SS_FCANTRCVMORE|SS_ISFCONNECTED)) == SS_ISFCONNECTED)
174: #define UPD_NFDS(x) if (nfds < (x)) nfds = (x)
175:
176: /*
177: * curtime kept to an accuracy of 1ms
178: */
179: #ifdef _WIN32
180: static void updtime(void)
181: {
182: struct _timeb tb;
183:
184: _ftime(&tb);
185: curtime = (u_int)tb.time * (u_int)1000;
186: curtime += (u_int)tb.millitm;
187: }
188: #else
189: static void updtime(void)
190: {
191: gettimeofday(&tt, 0);
1.1.1.4 ! root 192:
1.1 root 193: curtime = (u_int)tt.tv_sec * (u_int)1000;
194: curtime += (u_int)tt.tv_usec / (u_int)1000;
1.1.1.4 ! root 195:
1.1 root 196: if ((tt.tv_usec % 1000) >= 500)
197: curtime++;
198: }
199: #endif
200:
1.1.1.4 ! root 201: void slirp_select_fill(int *pnfds,
1.1 root 202: fd_set *readfds, fd_set *writefds, fd_set *xfds)
203: {
204: struct socket *so, *so_next;
205: struct timeval timeout;
206: int nfds;
207: int tmp_time;
208:
209: /* fail safe */
210: global_readfds = NULL;
211: global_writefds = NULL;
212: global_xfds = NULL;
1.1.1.4 ! root 213:
1.1 root 214: nfds = *pnfds;
215: /*
216: * First, TCP sockets
217: */
218: do_slowtimo = 0;
219: if (link_up) {
1.1.1.4 ! root 220: /*
1.1 root 221: * *_slowtimo needs calling if there are IP fragments
222: * in the fragment queue, or there are TCP connections active
223: */
224: do_slowtimo = ((tcb.so_next != &tcb) ||
225: ((struct ipasfrag *)&ipq != (struct ipasfrag *)ipq.next));
1.1.1.4 ! root 226:
1.1 root 227: for (so = tcb.so_next; so != &tcb; so = so_next) {
228: so_next = so->so_next;
1.1.1.4 ! root 229:
1.1 root 230: /*
231: * See if we need a tcp_fasttimo
232: */
233: if (time_fasttimo == 0 && so->so_tcpcb->t_flags & TF_DELACK)
234: time_fasttimo = curtime; /* Flag when we want a fasttimo */
1.1.1.4 ! root 235:
1.1 root 236: /*
237: * NOFDREF can include still connecting to local-host,
238: * newly socreated() sockets etc. Don't want to select these.
239: */
240: if (so->so_state & SS_NOFDREF || so->s == -1)
241: continue;
1.1.1.4 ! root 242:
1.1 root 243: /*
244: * Set for reading sockets which are accepting
245: */
246: if (so->so_state & SS_FACCEPTCONN) {
247: FD_SET(so->s, readfds);
248: UPD_NFDS(so->s);
249: continue;
250: }
1.1.1.4 ! root 251:
1.1 root 252: /*
253: * Set for writing sockets which are connecting
254: */
255: if (so->so_state & SS_ISFCONNECTING) {
256: FD_SET(so->s, writefds);
257: UPD_NFDS(so->s);
258: continue;
259: }
1.1.1.4 ! root 260:
1.1 root 261: /*
262: * Set for writing if we are connected, can send more, and
263: * we have something to send
264: */
265: if (CONN_CANFSEND(so) && so->so_rcv.sb_cc) {
266: FD_SET(so->s, writefds);
267: UPD_NFDS(so->s);
268: }
1.1.1.4 ! root 269:
1.1 root 270: /*
271: * Set for reading (and urgent data) if we are connected, can
272: * receive more, and we have room for it XXX /2 ?
273: */
274: if (CONN_CANFRCV(so) && (so->so_snd.sb_cc < (so->so_snd.sb_datalen/2))) {
275: FD_SET(so->s, readfds);
276: FD_SET(so->s, xfds);
277: UPD_NFDS(so->s);
278: }
279: }
1.1.1.4 ! root 280:
1.1 root 281: /*
282: * UDP sockets
283: */
284: for (so = udb.so_next; so != &udb; so = so_next) {
285: so_next = so->so_next;
1.1.1.4 ! root 286:
1.1 root 287: /*
288: * See if it's timed out
289: */
290: if (so->so_expire) {
291: if (so->so_expire <= curtime) {
292: udp_detach(so);
293: continue;
294: } else
295: do_slowtimo = 1; /* Let socket expire */
296: }
1.1.1.4 ! root 297:
1.1 root 298: /*
299: * When UDP packets are received from over the
300: * link, they're sendto()'d straight away, so
301: * no need for setting for writing
302: * Limit the number of packets queued by this session
303: * to 4. Note that even though we try and limit this
304: * to 4 packets, the session could have more queued
305: * if the packets needed to be fragmented
306: * (XXX <= 4 ?)
307: */
308: if ((so->so_state & SS_ISFCONNECTED) && so->so_queued <= 4) {
309: FD_SET(so->s, readfds);
310: UPD_NFDS(so->s);
311: }
312: }
313: }
1.1.1.4 ! root 314:
1.1 root 315: /*
316: * Setup timeout to use minimum CPU usage, especially when idle
317: */
1.1.1.4 ! root 318:
! 319: /*
1.1 root 320: * First, see the timeout needed by *timo
321: */
322: timeout.tv_sec = 0;
323: timeout.tv_usec = -1;
324: /*
325: * If a slowtimo is needed, set timeout to 500ms from the last
326: * slow timeout. If a fast timeout is needed, set timeout within
327: * 200ms of when it was requested.
328: */
329: if (do_slowtimo) {
330: /* XXX + 10000 because some select()'s aren't that accurate */
331: timeout.tv_usec = ((500 - (curtime - last_slowtimo)) * 1000) + 10000;
332: if (timeout.tv_usec < 0)
333: timeout.tv_usec = 0;
334: else if (timeout.tv_usec > 510000)
335: timeout.tv_usec = 510000;
1.1.1.4 ! root 336:
1.1 root 337: /* Can only fasttimo if we also slowtimo */
338: if (time_fasttimo) {
339: tmp_time = (200 - (curtime - time_fasttimo)) * 1000;
340: if (tmp_time < 0)
341: tmp_time = 0;
1.1.1.4 ! root 342:
1.1 root 343: /* Choose the smallest of the 2 */
344: if (tmp_time < timeout.tv_usec)
345: timeout.tv_usec = (u_int)tmp_time;
346: }
347: }
348: *pnfds = nfds;
1.1.1.4 ! root 349: }
1.1 root 350:
351: void slirp_select_poll(fd_set *readfds, fd_set *writefds, fd_set *xfds)
352: {
353: struct socket *so, *so_next;
354: int ret;
355:
356: global_readfds = readfds;
357: global_writefds = writefds;
358: global_xfds = xfds;
359:
360: /* Update time */
361: updtime();
1.1.1.4 ! root 362:
1.1 root 363: /*
1.1.1.4 ! root 364: * See if anything has timed out
1.1 root 365: */
366: if (link_up) {
367: if (time_fasttimo && ((curtime - time_fasttimo) >= 2)) {
368: tcp_fasttimo();
369: time_fasttimo = 0;
370: }
371: if (do_slowtimo && ((curtime - last_slowtimo) >= 499)) {
372: ip_slowtimo();
373: tcp_slowtimo();
374: last_slowtimo = curtime;
375: }
376: }
1.1.1.4 ! root 377:
1.1 root 378: /*
379: * Check sockets
380: */
381: if (link_up) {
382: /*
383: * Check TCP sockets
384: */
385: for (so = tcb.so_next; so != &tcb; so = so_next) {
386: so_next = so->so_next;
1.1.1.4 ! root 387:
1.1 root 388: /*
389: * FD_ISSET is meaningless on these sockets
390: * (and they can crash the program)
391: */
392: if (so->so_state & SS_NOFDREF || so->s == -1)
393: continue;
1.1.1.4 ! root 394:
1.1 root 395: /*
396: * Check for URG data
397: * This will soread as well, so no need to
398: * test for readfds below if this succeeds
399: */
400: if (FD_ISSET(so->s, xfds))
401: sorecvoob(so);
402: /*
403: * Check sockets for reading
404: */
405: else if (FD_ISSET(so->s, readfds)) {
406: /*
407: * Check for incoming connections
408: */
409: if (so->so_state & SS_FACCEPTCONN) {
410: tcp_connect(so);
411: continue;
412: } /* else */
413: ret = soread(so);
1.1.1.4 ! root 414:
1.1 root 415: /* Output it if we read something */
416: if (ret > 0)
417: tcp_output(sototcpcb(so));
418: }
1.1.1.4 ! root 419:
1.1 root 420: /*
421: * Check sockets for writing
422: */
423: if (FD_ISSET(so->s, writefds)) {
424: /*
425: * Check for non-blocking, still-connecting sockets
426: */
427: if (so->so_state & SS_ISFCONNECTING) {
428: /* Connected */
429: so->so_state &= ~SS_ISFCONNECTING;
1.1.1.4 ! root 430:
1.1 root 431: ret = send(so->s, &ret, 0, 0);
432: if (ret < 0) {
433: /* XXXXX Must fix, zero bytes is a NOP */
434: if (errno == EAGAIN || errno == EWOULDBLOCK ||
435: errno == EINPROGRESS || errno == ENOTCONN)
436: continue;
1.1.1.4 ! root 437:
1.1 root 438: /* else failed */
439: so->so_state = SS_NOFDREF;
440: }
441: /* else so->so_state &= ~SS_ISFCONNECTING; */
1.1.1.4 ! root 442:
1.1 root 443: /*
444: * Continue tcp_input
445: */
446: tcp_input((struct mbuf *)NULL, sizeof(struct ip), so);
447: /* continue; */
448: } else
449: ret = sowrite(so);
450: /*
1.1.1.4 ! root 451: * XXXXX If we wrote something (a lot), there
1.1 root 452: * could be a need for a window update.
453: * In the worst case, the remote will send
454: * a window probe to get things going again
455: */
456: }
1.1.1.4 ! root 457:
1.1 root 458: /*
459: * Probe a still-connecting, non-blocking socket
460: * to check if it's still alive
461: */
462: #ifdef PROBE_CONN
463: if (so->so_state & SS_ISFCONNECTING) {
464: ret = recv(so->s, (char *)&ret, 0,0);
1.1.1.4 ! root 465:
1.1 root 466: if (ret < 0) {
467: /* XXX */
468: if (errno == EAGAIN || errno == EWOULDBLOCK ||
469: errno == EINPROGRESS || errno == ENOTCONN)
470: continue; /* Still connecting, continue */
1.1.1.4 ! root 471:
1.1 root 472: /* else failed */
473: so->so_state = SS_NOFDREF;
1.1.1.4 ! root 474:
1.1 root 475: /* tcp_input will take care of it */
476: } else {
477: ret = send(so->s, &ret, 0,0);
478: if (ret < 0) {
479: /* XXX */
480: if (errno == EAGAIN || errno == EWOULDBLOCK ||
481: errno == EINPROGRESS || errno == ENOTCONN)
482: continue;
483: /* else failed */
484: so->so_state = SS_NOFDREF;
485: } else
486: so->so_state &= ~SS_ISFCONNECTING;
1.1.1.4 ! root 487:
1.1 root 488: }
489: tcp_input((struct mbuf *)NULL, sizeof(struct ip),so);
490: } /* SS_ISFCONNECTING */
491: #endif
492: }
1.1.1.4 ! root 493:
1.1 root 494: /*
495: * Now UDP sockets.
496: * Incoming packets are sent straight away, they're not buffered.
497: * Incoming UDP data isn't buffered either.
498: */
499: for (so = udb.so_next; so != &udb; so = so_next) {
500: so_next = so->so_next;
1.1.1.4 ! root 501:
1.1 root 502: if (so->s != -1 && FD_ISSET(so->s, readfds)) {
503: sorecvfrom(so);
504: }
505: }
506: }
1.1.1.4 ! root 507:
1.1 root 508: /*
509: * See if we can start outputting
510: */
511: if (if_queued && link_up)
512: if_start();
513:
514: /* clear global file descriptor sets.
515: * these reside on the stack in vl.c
516: * so they're unusable if we're not in
517: * slirp_select_fill or slirp_select_poll.
518: */
519: global_readfds = NULL;
520: global_writefds = NULL;
521: global_xfds = NULL;
522: }
523:
524: #define ETH_ALEN 6
525: #define ETH_HLEN 14
526:
527: #define ETH_P_IP 0x0800 /* Internet Protocol packet */
528: #define ETH_P_ARP 0x0806 /* Address Resolution packet */
529:
530: #define ARPOP_REQUEST 1 /* ARP request */
531: #define ARPOP_REPLY 2 /* ARP reply */
532:
1.1.1.4 ! root 533: struct ethhdr
1.1 root 534: {
535: unsigned char h_dest[ETH_ALEN]; /* destination eth addr */
536: unsigned char h_source[ETH_ALEN]; /* source ether addr */
537: unsigned short h_proto; /* packet type ID field */
538: };
539:
540: struct arphdr
541: {
542: unsigned short ar_hrd; /* format of hardware address */
543: unsigned short ar_pro; /* format of protocol address */
544: unsigned char ar_hln; /* length of hardware address */
545: unsigned char ar_pln; /* length of protocol address */
546: unsigned short ar_op; /* ARP opcode (command) */
547:
548: /*
549: * Ethernet looks like this : This bit is variable sized however...
550: */
551: unsigned char ar_sha[ETH_ALEN]; /* sender hardware address */
552: unsigned char ar_sip[4]; /* sender IP address */
553: unsigned char ar_tha[ETH_ALEN]; /* target hardware address */
554: unsigned char ar_tip[4]; /* target IP address */
555: };
556:
557: void arp_input(const uint8_t *pkt, int pkt_len)
558: {
559: struct ethhdr *eh = (struct ethhdr *)pkt;
560: struct arphdr *ah = (struct arphdr *)(pkt + ETH_HLEN);
561: uint8_t arp_reply[ETH_HLEN + sizeof(struct arphdr)];
562: struct ethhdr *reh = (struct ethhdr *)arp_reply;
563: struct arphdr *rah = (struct arphdr *)(arp_reply + ETH_HLEN);
564: int ar_op;
565: struct ex_list *ex_ptr;
566:
567: ar_op = ntohs(ah->ar_op);
568: switch(ar_op) {
569: case ARPOP_REQUEST:
570: if (!memcmp(ah->ar_tip, &special_addr, 3)) {
1.1.1.4 ! root 571: if (ah->ar_tip[3] == CTL_DNS || ah->ar_tip[3] == CTL_ALIAS)
1.1 root 572: goto arp_ok;
573: for (ex_ptr = exec_list; ex_ptr; ex_ptr = ex_ptr->ex_next) {
574: if (ex_ptr->ex_addr == ah->ar_tip[3])
575: goto arp_ok;
576: }
577: return;
578: arp_ok:
579: /* XXX: make an ARP request to have the client address */
580: memcpy(client_ethaddr, eh->h_source, ETH_ALEN);
581:
582: /* ARP request for alias/dns mac address */
583: memcpy(reh->h_dest, pkt + ETH_ALEN, ETH_ALEN);
584: memcpy(reh->h_source, special_ethaddr, ETH_ALEN - 1);
585: reh->h_source[5] = ah->ar_tip[3];
586: reh->h_proto = htons(ETH_P_ARP);
587:
588: rah->ar_hrd = htons(1);
589: rah->ar_pro = htons(ETH_P_IP);
590: rah->ar_hln = ETH_ALEN;
591: rah->ar_pln = 4;
592: rah->ar_op = htons(ARPOP_REPLY);
593: memcpy(rah->ar_sha, reh->h_source, ETH_ALEN);
594: memcpy(rah->ar_sip, ah->ar_tip, 4);
595: memcpy(rah->ar_tha, ah->ar_sha, ETH_ALEN);
596: memcpy(rah->ar_tip, ah->ar_sip, 4);
597: slirp_output(arp_reply, sizeof(arp_reply));
598: }
599: break;
600: default:
601: break;
602: }
603: }
604:
605: void slirp_input(const uint8_t *pkt, int pkt_len)
606: {
607: struct mbuf *m;
608: int proto;
609:
610: if (pkt_len < ETH_HLEN)
611: return;
1.1.1.4 ! root 612:
1.1 root 613: proto = ntohs(*(uint16_t *)(pkt + 12));
614: switch(proto) {
615: case ETH_P_ARP:
616: arp_input(pkt, pkt_len);
617: break;
618: case ETH_P_IP:
619: m = m_get();
620: if (!m)
621: return;
1.1.1.3 root 622: /* Note: we add to align the IP header */
623: m->m_len = pkt_len + 2;
624: memcpy(m->m_data + 2, pkt, pkt_len);
1.1 root 625:
1.1.1.3 root 626: m->m_data += 2 + ETH_HLEN;
627: m->m_len -= 2 + ETH_HLEN;
1.1 root 628:
629: ip_input(m);
630: break;
631: default:
632: break;
633: }
634: }
635:
636: /* output the IP packet to the ethernet device */
637: void if_encap(const uint8_t *ip_data, int ip_data_len)
638: {
639: uint8_t buf[1600];
640: struct ethhdr *eh = (struct ethhdr *)buf;
641:
642: if (ip_data_len + ETH_HLEN > sizeof(buf))
643: return;
644:
645: memcpy(eh->h_dest, client_ethaddr, ETH_ALEN);
646: memcpy(eh->h_source, special_ethaddr, ETH_ALEN - 1);
647: /* XXX: not correct */
648: eh->h_source[5] = CTL_ALIAS;
649: eh->h_proto = htons(ETH_P_IP);
650: memcpy(buf + sizeof(struct ethhdr), ip_data, ip_data_len);
651: slirp_output(buf, ip_data_len + ETH_HLEN);
652: }
653:
1.1.1.4 ! root 654: int slirp_redir(int is_udp, int host_port,
1.1 root 655: struct in_addr guest_addr, int guest_port)
656: {
657: if (is_udp) {
1.1.1.4 ! root 658: if (!udp_listen(htons(host_port), guest_addr.s_addr,
1.1 root 659: htons(guest_port), 0))
660: return -1;
661: } else {
1.1.1.4 ! root 662: if (!solisten(htons(host_port), guest_addr.s_addr,
1.1 root 663: htons(guest_port), 0))
664: return -1;
665: }
666: return 0;
667: }
668:
1.1.1.4 ! root 669: int slirp_add_exec(int do_pty, const char *args, int addr_low_byte,
1.1 root 670: int guest_port)
671: {
1.1.1.4 ! root 672: return add_exec(&exec_list, do_pty, (char *)args,
1.1 root 673: addr_low_byte, htons(guest_port));
674: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.