VirtualBox

source: vbox/trunk/src/VBox/Devices/Network/slirp/slirp.c@ 2959

Last change on this file since 2959 was 2959, checked in by vboxsync, 18 years ago

Don't log crap for malformed resolv.conf files.

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

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette