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