VirtualBox

source: vbox/trunk/src/VBox/NetworkServices/NAT/proxy.c@ 94068

Last change on this file since 94068 was 93115, checked in by vboxsync, 3 years ago

scm --update-copyright-year

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 19.4 KB
Line 
1/* $Id: proxy.c 93115 2022-01-01 11:31:46Z vboxsync $ */
2/** @file
3 * NAT Network - proxy setup and utilities.
4 */
5
6/*
7 * Copyright (C) 2013-2022 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18#define LOG_GROUP LOG_GROUP_NAT_SERVICE
19
20#include "winutils.h"
21
22#include "proxy.h"
23#include "proxy_pollmgr.h"
24#include "portfwd.h"
25
26#include "lwip/opt.h"
27
28#include "lwip/sys.h"
29#include "lwip/tcpip.h"
30
31#ifndef RT_OS_WINDOWS
32#include <sys/poll.h>
33#include <sys/socket.h>
34#include <netinet/in.h>
35#include <netinet/tcp.h>
36#include <arpa/inet.h>
37#include <fcntl.h>
38#include <stdio.h>
39#include <iprt/string.h>
40#include <unistd.h>
41#include <err.h>
42#else
43# include <iprt/string.h>
44#endif
45
46#if defined(SOCK_NONBLOCK) && defined(RT_OS_NETBSD) /* XXX: PR kern/47569 */
47# undef SOCK_NONBLOCK
48#endif
49
50#ifndef __arraycount
51# define __arraycount(a) (sizeof(a)/sizeof(a[0]))
52#endif
53
54static FNRTSTRFORMATTYPE proxy_sockerr_rtstrfmt;
55
56static SOCKET proxy_create_socket(int, int);
57
58volatile struct proxy_options *g_proxy_options;
59static sys_thread_t pollmgr_tid;
60
61/* XXX: for mapping loopbacks to addresses in our network (ip4) */
62struct netif *g_proxy_netif;
63
64
65/*
66 * Called on the lwip thread (aka tcpip thread) from tcpip_init() via
67 * its "tcpip_init_done" callback. Raw API is ok to use here
68 * (e.g. rtadvd), but netconn API is not.
69 */
70void
71proxy_init(struct netif *proxy_netif, struct proxy_options *opts)
72{
73 int status;
74
75 LWIP_ASSERT1(opts != NULL);
76 LWIP_UNUSED_ARG(proxy_netif);
77
78 status = RTStrFormatTypeRegister("sockerr", proxy_sockerr_rtstrfmt, NULL);
79 AssertRC(status);
80
81 g_proxy_options = opts;
82 g_proxy_netif = proxy_netif;
83
84#if 1
85 proxy_rtadvd_start(proxy_netif);
86#endif
87
88 /*
89 * XXX: We use stateless DHCPv6 only to report IPv6 address(es) of
90 * nameserver(s). Since we don't yet support IPv6 addresses in
91 * HostDnsService, there's no point in running DHCPv6.
92 */
93#if 0
94 dhcp6ds_init(proxy_netif);
95#endif
96
97 if (opts->tftp_root != NULL) {
98 tftpd_init(proxy_netif, opts->tftp_root);
99 }
100
101 status = pollmgr_init();
102 if (status < 0) {
103 errx(EXIT_FAILURE, "failed to initialize poll manager");
104 /* NOTREACHED */
105 }
106
107 pxtcp_init();
108 pxudp_init();
109
110 portfwd_init();
111
112 pxdns_init(proxy_netif);
113
114 pxping_init(proxy_netif, opts->icmpsock4, opts->icmpsock6);
115
116 pollmgr_tid = sys_thread_new("pollmgr_thread",
117 pollmgr_thread, NULL,
118 DEFAULT_THREAD_STACKSIZE,
119 DEFAULT_THREAD_PRIO);
120 if (!pollmgr_tid) {
121 errx(EXIT_FAILURE, "failed to create poll manager thread");
122 /* NOTREACHED */
123 }
124}
125
126
127#if !defined(RT_OS_WINDOWS)
128/**
129 * Formatter for %R[sockerr] - unix strerror_r() version.
130 */
131static DECLCALLBACK(size_t)
132proxy_sockerr_rtstrfmt(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput,
133 const char *pszType, const void *pvValue,
134 int cchWidth, int cchPrecision, unsigned int fFlags,
135 void *pvUser)
136{
137 const int error = (int)(intptr_t)pvValue;
138
139 const char *msg;
140 char buf[128];
141
142 NOREF(cchWidth);
143 NOREF(cchPrecision);
144 NOREF(fFlags);
145 NOREF(pvUser);
146
147 AssertReturn(strcmp(pszType, "sockerr") == 0, 0);
148
149 /* make sure return type mismatch is caught */
150 buf[0] = '\0';
151#if defined(RT_OS_LINUX) && defined(_GNU_SOURCE)
152 msg = strerror_r(error, buf, sizeof(buf));
153#else
154 strerror_r(error, buf, sizeof(buf));
155 msg = buf;
156#endif
157 return RTStrFormat(pfnOutput, pvArgOutput, NULL, NULL, "%s", msg);
158}
159
160#else /* RT_OS_WINDOWS */
161
162/**
163 * Formatter for %R[sockerr] - windows FormatMessage() version.
164 */
165static DECLCALLBACK(size_t)
166proxy_sockerr_rtstrfmt(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput,
167 const char *pszType, const void *pvValue,
168 int cchWidth, int cchPrecision, unsigned int fFlags,
169 void *pvUser)
170{
171 const int error = (int)(intptr_t)pvValue;
172 size_t cb = 0;
173
174 NOREF(cchWidth);
175 NOREF(cchPrecision);
176 NOREF(fFlags);
177 NOREF(pvUser);
178
179 AssertReturn(strcmp(pszType, "sockerr") == 0, 0);
180
181 /*
182 * XXX: Windows strerror() doesn't handle posix error codes, but
183 * since winsock uses its own, it shouldn't be much of a problem.
184 * If you see a strange error message, it's probably from
185 * FormatMessage() for an error from <WinError.h> that has the
186 * same numeric value.
187 */
188 if (error < _sys_nerr) {
189 char buf[128] = "";
190 int status;
191
192 status = strerror_s(buf, sizeof(buf), error);
193 if (status == 0) {
194 if (strcmp(buf, "Unknown error") == 0) {
195 /* windows strerror() doesn't add the numeric value */
196 cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, NULL,
197 "Unknown error: %d", error);
198 }
199 else {
200 cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, NULL,
201 "%s", buf);
202 }
203 }
204 else {
205 cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, NULL,
206 "Unknown error: %d", error);
207 }
208 }
209 else {
210 DWORD nchars;
211 char *msg = NULL;
212
213 nchars = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM
214 | FORMAT_MESSAGE_ALLOCATE_BUFFER,
215 NULL, error, LANG_NEUTRAL,
216 (LPSTR)&msg, 0,
217 NULL);
218 if (nchars == 0 || msg == NULL) {
219 cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, NULL,
220 "Unknown error: %d", error);
221 }
222 else {
223 /* FormatMessage() "helpfully" adds newline; get rid of it */
224 char *crpos = strchr(msg, '\r');
225 if (crpos != NULL) {
226 *crpos = '\0';
227 }
228
229 cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, NULL,
230 "%s", msg);
231 }
232
233 if (msg != NULL) {
234 LocalFree(msg);
235 }
236 }
237
238 return cb;
239}
240#endif /* RT_OS_WINDOWS */
241
242
243/**
244 * Send static callback message from poll manager thread to lwip
245 * thread, scheduling a function call in lwip thread context.
246 *
247 * XXX: Existing lwip api only provides non-blocking version for this.
248 * It may fail when lwip thread is not running (mbox invalid) or if
249 * post failed (mbox full). How to handle these?
250 */
251void
252proxy_lwip_post(struct tcpip_msg *msg)
253{
254 struct tcpip_callback_msg *m;
255 err_t error;
256
257 LWIP_ASSERT1(msg != NULL);
258
259 /*
260 * lwip plays games with fake incomplete struct tag to enforce API
261 */
262 m = (struct tcpip_callback_msg *)msg;
263 error = tcpip_callbackmsg(m);
264
265 if (error == ERR_VAL) {
266 /* XXX: lwip thread is not running (mbox invalid) */
267 LWIP_ASSERT1(error != ERR_VAL);
268 }
269
270 LWIP_ASSERT1(error == ERR_OK);
271}
272
273
274/**
275 * Create a non-blocking socket. Disable SIGPIPE for TCP sockets if
276 * possible. On Linux it's not possible and should be disabled for
277 * each send(2) individually.
278 */
279static SOCKET
280proxy_create_socket(int sdom, int stype)
281{
282 SOCKET s;
283 int stype_and_flags;
284 int status;
285
286 LWIP_UNUSED_ARG(status); /* depends on ifdefs */
287
288
289 stype_and_flags = stype;
290
291#if defined(SOCK_NONBLOCK)
292 stype_and_flags |= SOCK_NONBLOCK;
293#endif
294
295 /*
296 * Disable SIGPIPE on disconnected socket. It might be easier to
297 * forgo it and just use MSG_NOSIGNAL on each send*(2), since we
298 * have to do it for Linux anyway, but Darwin does NOT have that
299 * flag (but has SO_NOSIGPIPE socket option).
300 */
301#if !defined(SOCK_NOSIGPIPE) && !defined(SO_NOSIGPIPE) && !defined(MSG_NOSIGNAL)
302#if 0 /* XXX: Solaris has neither, the program should ignore SIGPIPE globally */
303#error Need a way to disable SIGPIPE on connection oriented sockets!
304#endif
305#endif
306
307#if defined(SOCK_NOSIGPIPE)
308 if (stype == SOCK_STREAM) {
309 stype_and_flags |= SOCK_NOSIGPIPE;
310 }
311#endif
312
313 s = socket(sdom, stype_and_flags, 0);
314 if (s == INVALID_SOCKET) {
315 DPRINTF(("socket: %R[sockerr]\n", SOCKERRNO()));
316 return INVALID_SOCKET;
317 }
318
319#if defined(RT_OS_WINDOWS)
320 {
321 u_long mode = 1;
322 status = ioctlsocket(s, FIONBIO, &mode);
323 if (status == SOCKET_ERROR) {
324 DPRINTF(("FIONBIO: %R[sockerr]\n", SOCKERRNO()));
325 closesocket(s);
326 return INVALID_SOCKET;
327 }
328 }
329#elif !defined(SOCK_NONBLOCK)
330 {
331 int sflags;
332
333 sflags = fcntl(s, F_GETFL, 0);
334 if (sflags < 0) {
335 DPRINTF(("F_GETFL: %R[sockerr]\n", SOCKERRNO()));
336 closesocket(s);
337 return INVALID_SOCKET;
338 }
339
340 status = fcntl(s, F_SETFL, sflags | O_NONBLOCK);
341 if (status < 0) {
342 DPRINTF(("O_NONBLOCK: %R[sockerr]\n", SOCKERRNO()));
343 closesocket(s);
344 return INVALID_SOCKET;
345 }
346 }
347#endif
348
349#if !defined(SOCK_NOSIGPIPE) && defined(SO_NOSIGPIPE)
350 if (stype == SOCK_STREAM) {
351 int on = 1;
352 const socklen_t onlen = sizeof(on);
353
354 status = setsockopt(s, SOL_SOCKET, SO_NOSIGPIPE, &on, onlen);
355 if (status < 0) {
356 DPRINTF(("SO_NOSIGPIPE: %R[sockerr]\n", SOCKERRNO()));
357 closesocket(s);
358 return INVALID_SOCKET;
359 }
360 }
361#endif
362
363 /*
364 * Disable the Nagle algorithm. Otherwise the host may hold back
365 * packets that the guest wants to go out, causing potentially
366 * horrible performance. The guest is already applying the Nagle
367 * algorithm (or not) the way it wants.
368 */
369 if (stype == SOCK_STREAM) {
370 int on = 1;
371 const socklen_t onlen = sizeof(on);
372
373 status = setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char *)&on, onlen);
374 if (status < 0) {
375 DPRINTF(("TCP_NODELAY: %R[sockerr]\n", SOCKERRNO()));
376 }
377 }
378
379#if defined(RT_OS_WINDOWS)
380 /*
381 * lwIP only holds one packet of "refused data" for us. Proxy
382 * relies on OS socket send buffer and doesn't do its own
383 * buffering. Unfortunately on Windows send buffer is very small
384 * (8K by default) and is not dynamically adpated by the OS it
385 * seems. So a single large write will fill it up and that will
386 * make lwIP drop segments, causing guest TCP into pathologic
387 * resend patterns. As a quick and dirty fix just bump it up.
388 */
389 if (stype == SOCK_STREAM) {
390 int sndbuf;
391 socklen_t optlen = sizeof(sndbuf);
392
393 status = getsockopt(s, SOL_SOCKET, SO_SNDBUF, (char *)&sndbuf, &optlen);
394 if (status == 0) {
395 if (sndbuf < 64 * 1024) {
396 sndbuf = 64 * 1024;
397 status = setsockopt(s, SOL_SOCKET, SO_SNDBUF,
398 (char *)&sndbuf, optlen);
399 if (status != 0) {
400 DPRINTF(("SO_SNDBUF: setsockopt: %R[sockerr]\n", SOCKERRNO()));
401 }
402 }
403 }
404 else {
405 DPRINTF(("SO_SNDBUF: getsockopt: %R[sockerr]\n", SOCKERRNO()));
406 }
407 }
408#endif
409
410 return s;
411}
412
413
414#ifdef RT_OS_LINUX
415/**
416 * Fixup a socket returned by accept(2).
417 *
418 * On Linux a socket returned by accept(2) does NOT inherit the socket
419 * options from the listening socket! We need to repeat parts of the
420 * song and dance we did above to make it non-blocking.
421 */
422int
423proxy_fixup_accepted_socket(SOCKET s)
424{
425 int sflags;
426 int status;
427
428 sflags = fcntl(s, F_GETFL, 0);
429 if (sflags < 0) {
430 DPRINTF(("F_GETFL: %R[sockerr]\n", SOCKERRNO()));
431 return -1;
432 }
433
434 status = fcntl(s, F_SETFL, sflags | O_NONBLOCK);
435 if (status < 0) {
436 DPRINTF(("O_NONBLOCK: %R[sockerr]\n", SOCKERRNO()));
437 return -1;
438 }
439
440 return 0;
441}
442#endif /* RT_OS_LINUX */
443
444
445/**
446 * Create a socket for outbound connection to dst_addr:dst_port.
447 *
448 * The socket is non-blocking and TCP sockets has SIGPIPE disabled if
449 * possible. On Linux it's not possible and should be disabled for
450 * each send(2) individually.
451 */
452SOCKET
453proxy_connected_socket(int sdom, int stype,
454 ipX_addr_t *dst_addr, u16_t dst_port)
455{
456 struct sockaddr_in6 dst_sin6;
457 struct sockaddr_in dst_sin;
458 struct sockaddr *pdst_sa;
459 socklen_t dst_sa_len;
460 void *pdst_addr;
461 const struct sockaddr *psrc_sa;
462 socklen_t src_sa_len;
463 int status;
464 int sockerr;
465 SOCKET s;
466
467 LWIP_ASSERT1(sdom == PF_INET || sdom == PF_INET6);
468 LWIP_ASSERT1(stype == SOCK_STREAM || stype == SOCK_DGRAM);
469
470 DPRINTF(("---> %s ", stype == SOCK_STREAM ? "TCP" : "UDP"));
471 if (sdom == PF_INET6) {
472 pdst_sa = (struct sockaddr *)&dst_sin6;
473 pdst_addr = (void *)&dst_sin6.sin6_addr;
474
475 memset(&dst_sin6, 0, sizeof(dst_sin6));
476#if HAVE_SA_LEN
477 dst_sin6.sin6_len =
478#endif
479 dst_sa_len = sizeof(dst_sin6);
480 dst_sin6.sin6_family = AF_INET6;
481 memcpy(&dst_sin6.sin6_addr, &dst_addr->ip6, sizeof(ip6_addr_t));
482 dst_sin6.sin6_port = htons(dst_port);
483
484 DPRINTF(("[%RTnaipv6]:%d ", &dst_sin6.sin6_addr, dst_port));
485 }
486 else { /* sdom = PF_INET */
487 pdst_sa = (struct sockaddr *)&dst_sin;
488 pdst_addr = (void *)&dst_sin.sin_addr;
489
490 memset(&dst_sin, 0, sizeof(dst_sin));
491#if HAVE_SA_LEN
492 dst_sin.sin_len =
493#endif
494 dst_sa_len = sizeof(dst_sin);
495 dst_sin.sin_family = AF_INET;
496 dst_sin.sin_addr.s_addr = dst_addr->ip4.addr; /* byte-order? */
497 dst_sin.sin_port = htons(dst_port);
498
499 DPRINTF(("%RTnaipv4:%d ", dst_sin.sin_addr.s_addr, dst_port));
500 }
501
502 s = proxy_create_socket(sdom, stype);
503 if (s == INVALID_SOCKET) {
504 return INVALID_SOCKET;
505 }
506 DPRINTF(("socket %d\n", s));
507
508 /** @todo needs locking if dynamic modifyvm is allowed */
509 if (sdom == PF_INET6) {
510 psrc_sa = (const struct sockaddr *)g_proxy_options->src6;
511 src_sa_len = sizeof(struct sockaddr_in6);
512 }
513 else {
514 psrc_sa = (const struct sockaddr *)g_proxy_options->src4;
515 src_sa_len = sizeof(struct sockaddr_in);
516 }
517 if (psrc_sa != NULL) {
518 status = bind(s, psrc_sa, src_sa_len);
519 if (status == SOCKET_ERROR) {
520 sockerr = SOCKERRNO();
521 DPRINTF(("socket %d: bind: %R[sockerr]\n", s, sockerr));
522 closesocket(s);
523 SET_SOCKERRNO(sockerr);
524 return INVALID_SOCKET;
525 }
526 }
527
528 status = connect(s, pdst_sa, dst_sa_len);
529 if (status == SOCKET_ERROR
530#if !defined(RT_OS_WINDOWS)
531 && SOCKERRNO() != EINPROGRESS
532#else
533 && SOCKERRNO() != EWOULDBLOCK
534#endif
535 )
536 {
537 sockerr = SOCKERRNO();
538 DPRINTF(("socket %d: connect: %R[sockerr]\n", s, sockerr));
539 closesocket(s);
540 SET_SOCKERRNO(sockerr);
541 return INVALID_SOCKET;
542 }
543
544 return s;
545}
546
547
548/**
549 * Create a socket for inbound (port-forwarded) connections to
550 * src_addr (port is part of sockaddr, so not a separate argument).
551 *
552 * The socket is non-blocking and TCP sockets has SIGPIPE disabled if
553 * possible. On Linux it's not possible and should be disabled for
554 * each send(2) individually.
555 *
556 * TODO?: Support v6-mapped v4 so that user can specify she wants
557 * "udp" and get both versions?
558 */
559SOCKET
560proxy_bound_socket(int sdom, int stype, struct sockaddr *src_addr)
561{
562 SOCKET s;
563 int on;
564 const socklen_t onlen = sizeof(on);
565 int status;
566 int sockerr;
567
568 s = proxy_create_socket(sdom, stype);
569 if (s == INVALID_SOCKET) {
570 return INVALID_SOCKET;
571 }
572 DPRINTF(("socket %d\n", s));
573
574 on = 1;
575 status = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&on, onlen);
576 if (status < 0) { /* not good, but not fatal */
577 DPRINTF(("SO_REUSEADDR: %R[sockerr]\n", SOCKERRNO()));
578 }
579
580 status = bind(s, src_addr,
581 sdom == PF_INET ?
582 sizeof(struct sockaddr_in)
583 : sizeof(struct sockaddr_in6));
584 if (status == SOCKET_ERROR) {
585 sockerr = SOCKERRNO();
586 DPRINTF(("bind: %R[sockerr]\n", sockerr));
587 closesocket(s);
588 SET_SOCKERRNO(sockerr);
589 return INVALID_SOCKET;
590 }
591
592 if (stype == SOCK_STREAM) {
593 status = listen(s, 5);
594 if (status == SOCKET_ERROR) {
595 sockerr = SOCKERRNO();
596 DPRINTF(("listen: %R[sockerr]\n", sockerr));
597 closesocket(s);
598 SET_SOCKERRNO(sockerr);
599 return INVALID_SOCKET;
600 }
601 }
602
603 return s;
604}
605
606
607void
608proxy_reset_socket(SOCKET s)
609{
610 struct linger linger;
611
612 linger.l_onoff = 1;
613 linger.l_linger = 0;
614
615 /* On Windows we can run into issue here, perhaps SO_LINGER isn't enough, and
616 * we should use WSA{Send,Recv}Disconnect instead.
617 *
618 * Links for the reference:
619 * http://msdn.microsoft.com/en-us/library/windows/desktop/ms738547%28v=vs.85%29.aspx
620 * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4468997
621 */
622 setsockopt(s, SOL_SOCKET, SO_LINGER, (char *)&linger, sizeof(linger));
623
624 closesocket(s);
625}
626
627
628int
629proxy_sendto(SOCKET sock, struct pbuf *p, void *name, size_t namelen)
630{
631 struct pbuf *q;
632 size_t i, clen;
633#ifndef RT_OS_WINDOWS
634 struct msghdr mh;
635 ssize_t nsent;
636#else
637 DWORD nsent;
638#endif
639 int rc;
640 IOVEC fixiov[8]; /* fixed size (typical case) */
641 const size_t fixiovsize = sizeof(fixiov)/sizeof(fixiov[0]);
642 IOVEC *dyniov; /* dynamically sized */
643 IOVEC *iov;
644 int error = 0;
645
646 /*
647 * Static iov[] is usually enough since UDP protocols use small
648 * datagrams to avoid fragmentation, but be prepared.
649 */
650 clen = pbuf_clen(p);
651 if (clen > fixiovsize) {
652 /*
653 * XXX: TODO: check that clen is shorter than IOV_MAX
654 */
655 dyniov = (IOVEC *)malloc(clen * sizeof(*dyniov));
656 if (dyniov == NULL) {
657 error = -errno; /* sic: not a socket error */
658 goto out;
659 }
660 iov = dyniov;
661 }
662 else {
663 dyniov = NULL;
664 iov = fixiov;
665 }
666
667
668 for (q = p, i = 0; i < clen; q = q->next, ++i) {
669 LWIP_ASSERT1(q != NULL);
670
671 IOVEC_SET_BASE(iov[i], q->payload);
672 IOVEC_SET_LEN(iov[i], q->len);
673 }
674
675#ifndef RT_OS_WINDOWS
676 memset(&mh, 0, sizeof(mh));
677 mh.msg_name = name;
678 mh.msg_namelen = namelen;
679 mh.msg_iov = iov;
680 mh.msg_iovlen = clen;
681
682 nsent = sendmsg(sock, &mh, 0);
683 rc = (nsent >= 0) ? 0 : SOCKET_ERROR;
684#else
685 rc = WSASendTo(sock, iov, (DWORD)clen, &nsent, 0,
686 name, (int)namelen, NULL, NULL);
687#endif
688 if (rc == SOCKET_ERROR) {
689 error = SOCKERRNO();
690 DPRINTF(("%s: socket %d: sendmsg: %R[sockerr]\n",
691 __func__, sock, error));
692 error = -error;
693 }
694
695 out:
696 if (dyniov != NULL) {
697 free(dyniov);
698 }
699 return error;
700}
701
702
703static const char *lwiperr[] = {
704 "ERR_OK",
705 "ERR_MEM",
706 "ERR_BUF",
707 "ERR_TIMEOUT",
708 "ERR_RTE",
709 "ERR_INPROGRESS",
710 "ERR_VAL",
711 "ERR_WOULDBLOCK",
712 "ERR_USE",
713 "ERR_ISCONN",
714 "ERR_ABRT",
715 "ERR_RST",
716 "ERR_CLSD",
717 "ERR_CONN",
718 "ERR_ARG",
719 "ERR_IF"
720};
721
722
723const char *
724proxy_lwip_strerr(err_t error)
725{
726 static char buf[32];
727 int e = -error;
728
729 if (0 <= e && e < (int)__arraycount(lwiperr)) {
730 return lwiperr[e];
731 }
732 else {
733 RTStrPrintf(buf, sizeof(buf), "unknown error %d", error);
734 return buf;
735 }
736}
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