VirtualBox

source: vbox/trunk/src/VBox/NetworkServices/NAT/pxping.c@ 51702

最後變更 在這個檔案從51702是 51702,由 vboxsync 提交於 11 年 前

NAT/Net: Cosmetic follow up to r94469 - use ipoff variable in debug
printf, don't fetch it from the header again.

  • 屬性 svn:eol-style 設為 native
  • 屬性 svn:keywords 設為 Author Date Id Revision
檔案大小: 54.2 KB
 
1/* -*- indent-tabs-mode: nil; -*- */
2#define LOG_GROUP LOG_GROUP_NAT_SERVICE
3
4#include "winutils.h"
5#include "proxy.h"
6#include "proxy_pollmgr.h"
7#include "pxremap.h"
8
9#include <iprt/string.h>
10
11#ifndef RT_OS_WINDOWS
12#include <sys/types.h>
13#include <sys/socket.h>
14#ifdef RT_OS_DARWIN
15# define __APPLE_USE_RFC_3542
16#endif
17#include <netinet/in.h>
18#include <poll.h>
19#include <stdint.h>
20#include <stdio.h>
21#include <stdlib.h>
22#include <string.h>
23#else
24#include <iprt/stdint.h>
25#include <stdio.h>
26#include <stdlib.h>
27#include <string.h>
28#include "winpoll.h"
29#endif
30
31#include "lwip/opt.h"
32
33#include "lwip/sys.h"
34#include "lwip/tcpip.h"
35#include "lwip/inet_chksum.h"
36#include "lwip/ip.h"
37#include "lwip/icmp.h"
38
39#if defined(RT_OS_LINUX) && !defined(__USE_GNU)
40#if __GLIBC_PREREQ(2, 8)
41/*
42 * XXX: This is gross. in6_pktinfo is now hidden behind _GNU_SOURCE
43 * https://sourceware.org/bugzilla/show_bug.cgi?id=6775
44 *
45 * But in older glibc versions, e.g. RHEL5, it is not! I don't want
46 * to deal with _GNU_SOURCE now, so as a kludge check for glibc
47 * version. It seems the __USE_GNU guard was introduced in 2.8.
48 */
49struct in6_pktinfo {
50 struct in6_addr ipi6_addr;
51 unsigned int ipi6_ifindex;
52};
53#endif /* __GLIBC_PREREQ */
54#endif /* RT_OS_LINUX && !__USE_GNU */
55
56
57/* forward */
58struct ping_pcb;
59
60
61/**
62 * Global state for ping proxy collected in one entity to minimize
63 * globals. There's only one instance of this structure.
64 *
65 * Raw ICMP sockets are promiscuous, so it doesn't make sense to have
66 * multiple. If this code ever needs to support multiple netifs, the
67 * netif member should be exiled into "pcb".
68 */
69struct pxping {
70 SOCKET sock4;
71
72#if defined(RT_OS_DARWIN) || defined(RT_OS_SOLARIS)
73# define DF_WITH_IP_HDRINCL
74 int hdrincl;
75#else
76 int df;
77#endif
78 int ttl;
79 int tos;
80
81 SOCKET sock6;
82#ifdef RT_OS_WINDOWS
83 LPFN_WSARECVMSG pfWSARecvMsg6;
84#endif
85 int hopl;
86
87 struct pollmgr_handler pmhdl4;
88 struct pollmgr_handler pmhdl6;
89
90 struct netif *netif;
91
92 /**
93 * Protect lwIP and pmgr accesses to the list of pcbs.
94 */
95 sys_mutex_t lock;
96
97 /*
98 * We need to find pcbs both from the guest side and from the host
99 * side. If we need to support industrial grade ping throughput,
100 * we will need two pcb hashes. For now, a short linked list
101 * should be enough. Cf. pxping_pcb_for_request() and
102 * pxping_pcb_for_reply().
103 */
104#define PXPING_MAX_PCBS 8
105 size_t npcbs;
106 struct ping_pcb *pcbs;
107
108#define TIMEOUT 5
109 int timer_active;
110 size_t timeout_slot;
111 struct ping_pcb *timeout_list[TIMEOUT];
112};
113
114
115/**
116 * Quasi PCB for ping.
117 */
118struct ping_pcb {
119 ipX_addr_t src;
120 ipX_addr_t dst;
121
122 u8_t is_ipv6;
123 u8_t is_mapped;
124
125 u16_t guest_id;
126 u16_t host_id;
127
128 /**
129 * Desired slot in pxping::timeout_list. See pxping_timer().
130 */
131 size_t timeout_slot;
132
133 /**
134 * Chaining for pxping::timeout_list
135 */
136 struct ping_pcb **pprev_timeout;
137 struct ping_pcb *next_timeout;
138
139 /**
140 * Chaining for pxping::pcbs
141 */
142 struct ping_pcb *next;
143
144 union {
145 struct sockaddr_in sin;
146 struct sockaddr_in6 sin6;
147 } peer;
148};
149
150
151/**
152 * lwIP thread callback message for IPv4 ping.
153 *
154 * We pass raw IP datagram for ip_output_if() so we only need pbuf and
155 * netif (from pxping).
156 */
157struct ping_msg {
158 struct tcpip_msg msg;
159 struct pxping *pxping;
160 struct pbuf *p;
161};
162
163
164/**
165 * lwIP thread callback message for IPv6 ping.
166 *
167 * We cannot obtain raw IPv6 datagram from host without extra trouble,
168 * so we pass ICMPv6 payload in pbuf and also other parameters to
169 * ip6_output_if().
170 */
171struct ping6_msg {
172 struct tcpip_msg msg;
173 struct pxping *pxping;
174 struct pbuf *p;
175 ip6_addr_t src, dst;
176 int hopl, tclass;
177};
178
179
180#ifdef RT_OS_WINDOWS
181static int pxping_init_windows(struct pxping *pxping);
182#endif
183static void pxping_recv4(void *arg, struct pbuf *p);
184static void pxping_recv6(void *arg, struct pbuf *p);
185
186static void pxping_timer(void *arg);
187static void pxping_timer_needed(struct pxping *pxping);
188
189static struct ping_pcb *pxping_pcb_for_request(struct pxping *pxping,
190 int is_ipv6,
191 ipX_addr_t *src, ipX_addr_t *dst,
192 u16_t guest_id);
193static struct ping_pcb *pxping_pcb_for_reply(struct pxping *pxping, int is_ipv6,
194 ipX_addr_t *dst, u16_t host_id);
195
196static FNRTSTRFORMATTYPE pxping_pcb_rtstrfmt;
197static struct ping_pcb *pxping_pcb_allocate(struct pxping *pxping);
198static void pxping_pcb_register(struct pxping *pxping, struct ping_pcb *pcb);
199static void pxping_pcb_deregister(struct pxping *pxping, struct ping_pcb *pcb);
200static void pxping_pcb_delete(struct pxping *pxping, struct ping_pcb *pcb);
201static void pxping_timeout_add(struct pxping *pxping, struct ping_pcb *pcb);
202static void pxping_timeout_del(struct pxping *pxping, struct ping_pcb *pcb);
203
204static int pxping_pmgr_pump(struct pollmgr_handler *handler, SOCKET fd, int revents);
205
206static void pxping_pmgr_icmp4(struct pxping *pxping);
207static void pxping_pmgr_icmp4_echo(struct pxping *pxping,
208 u16_t iplen, struct sockaddr_in *peer);
209static void pxping_pmgr_icmp4_error(struct pxping *pxping,
210 u16_t iplen, struct sockaddr_in *peer);
211static void pxping_pmgr_icmp6(struct pxping *pxping);
212static void pxping_pmgr_icmp6_echo(struct pxping *pxping,
213 ip6_addr_t *src, ip6_addr_t *dst,
214 int hopl, int tclass, u16_t icmplen);
215static void pxping_pmgr_icmp6_error(struct pxping *pxping,
216 ip6_addr_t *src, ip6_addr_t *dst,
217 int hopl, int tclass, u16_t icmplen);
218
219static void pxping_pmgr_forward_inbound(struct pxping *pxping, u16_t iplen);
220static void pxping_pcb_forward_inbound(void *arg);
221
222static void pxping_pmgr_forward_inbound6(struct pxping *pxping,
223 ip6_addr_t *src, ip6_addr_t *dst,
224 u8_t hopl, u8_t tclass,
225 u16_t icmplen);
226static void pxping_pcb_forward_inbound6(void *arg);
227
228/*
229 * NB: This is not documented except in RTFS.
230 *
231 * If ip_output_if() is passed dest == NULL then it treats p as
232 * complete IP packet with payload pointing to the IP header. It does
233 * not build IP header, ignores all header-related arguments, fetches
234 * real destination from the header in the pbuf and outputs pbuf to
235 * the specified netif.
236 */
237#define ip_raw_output_if(p, netif) \
238 (ip_output_if((p), NULL, NULL, 0, 0, 0, (netif)))
239
240
241
242static struct pxping g_pxping;
243
244
245err_t
246pxping_init(struct netif *netif, SOCKET sock4, SOCKET sock6)
247{
248 const int on = 1;
249 int status;
250
251 if (sock4 == INVALID_SOCKET && sock6 == INVALID_SOCKET) {
252 return ERR_VAL;
253 }
254
255 g_pxping.netif = netif;
256 sys_mutex_new(&g_pxping.lock);
257
258 g_pxping.sock4 = sock4;
259 if (g_pxping.sock4 != INVALID_SOCKET) {
260#ifdef DF_WITH_IP_HDRINCL
261 g_pxping.hdrincl = 0;
262#else
263 g_pxping.df = -1;
264#endif
265 g_pxping.ttl = -1;
266 g_pxping.tos = 0;
267
268#ifdef RT_OS_LINUX
269 {
270 const int dont = IP_PMTUDISC_DONT;
271 status = setsockopt(sock4, IPPROTO_IP, IP_MTU_DISCOVER,
272 &dont, sizeof(dont));
273 if (status != 0) {
274 DPRINTF(("IP_MTU_DISCOVER: %R[sockerr]\n", SOCKERRNO()));
275 }
276 }
277#endif /* RT_OS_LINUX */
278
279 g_pxping.pmhdl4.callback = pxping_pmgr_pump;
280 g_pxping.pmhdl4.data = (void *)&g_pxping;
281 g_pxping.pmhdl4.slot = -1;
282 pollmgr_add(&g_pxping.pmhdl4, g_pxping.sock4, POLLIN);
283
284 ping_proxy_accept(pxping_recv4, &g_pxping);
285 }
286
287 g_pxping.sock6 = sock6;
288#ifdef RT_OS_WINDOWS
289 /* we need recvmsg */
290 if (g_pxping.sock6 != INVALID_SOCKET) {
291 status = pxping_init_windows(&g_pxping);
292 if (status == SOCKET_ERROR) {
293 g_pxping.sock6 = INVALID_SOCKET;
294 /* close(sock6); */
295 }
296 }
297#endif
298 if (g_pxping.sock6 != INVALID_SOCKET) {
299 g_pxping.hopl = -1;
300
301#if !defined(IPV6_RECVPKTINFO)
302#define IPV6_RECVPKTINFO (IPV6_PKTINFO)
303#endif
304 status = setsockopt(sock6, IPPROTO_IPV6, IPV6_RECVPKTINFO,
305 (const char *)&on, sizeof(on));
306 if (status < 0) {
307 DPRINTF(("IPV6_RECVPKTINFO: %R[sockerr]\n", SOCKERRNO()));
308 /* XXX: for now this is fatal */
309 }
310
311#if !defined(IPV6_RECVHOPLIMIT)
312#define IPV6_RECVHOPLIMIT (IPV6_HOPLIMIT)
313#endif
314 status = setsockopt(sock6, IPPROTO_IPV6, IPV6_RECVHOPLIMIT,
315 (const char *)&on, sizeof(on));
316 if (status < 0) {
317 DPRINTF(("IPV6_RECVHOPLIMIT: %R[sockerr]\n", SOCKERRNO()));
318 }
319
320#ifdef IPV6_RECVTCLASS /* new in RFC 3542, there's no RFC 2292 counterpart */
321 /* TODO: IPV6_RECVTCLASS */
322#endif
323
324 g_pxping.pmhdl6.callback = pxping_pmgr_pump;
325 g_pxping.pmhdl6.data = (void *)&g_pxping;
326 g_pxping.pmhdl6.slot = -1;
327 pollmgr_add(&g_pxping.pmhdl6, g_pxping.sock6, POLLIN);
328
329 ping6_proxy_accept(pxping_recv6, &g_pxping);
330 }
331
332 status = RTStrFormatTypeRegister("ping_pcb", pxping_pcb_rtstrfmt, NULL);
333 AssertRC(status);
334
335 return ERR_OK;
336}
337
338
339#ifdef RT_OS_WINDOWS
340static int
341pxping_init_windows(struct pxping *pxping)
342{
343 GUID WSARecvMsgGUID = WSAID_WSARECVMSG;
344 DWORD nread;
345 int status;
346
347 pxping->pfWSARecvMsg6 = NULL;
348 status = WSAIoctl(pxping->sock6,
349 SIO_GET_EXTENSION_FUNCTION_POINTER,
350 &WSARecvMsgGUID, sizeof(WSARecvMsgGUID),
351 &pxping->pfWSARecvMsg6, sizeof(pxping->pfWSARecvMsg6),
352 &nread,
353 NULL, NULL);
354 return status;
355}
356#endif /* RT_OS_WINDOWS */
357
358
359static u32_t
360chksum_delta_16(u16_t oval, u16_t nval)
361{
362 u32_t sum = (u16_t)~oval;
363 sum += nval;
364 return sum;
365}
366
367
368static u32_t
369chksum_update_16(u16_t *oldp, u16_t nval)
370{
371 u32_t sum = chksum_delta_16(*oldp, nval);
372 *oldp = nval;
373 return sum;
374}
375
376
377static u32_t
378chksum_delta_32(u32_t oval, u32_t nval)
379{
380 u32_t sum = ~oval;
381 sum = FOLD_U32T(sum);
382 sum += FOLD_U32T(nval);
383 return sum;
384}
385
386
387static u32_t
388chksum_update_32(u32_t *oldp, u32_t nval)
389{
390 u32_t sum = chksum_delta_32(*oldp, nval);
391 *oldp = nval;
392 return sum;
393}
394
395
396static u32_t
397chksum_delta_ipv6(const ip6_addr_t *oldp, const ip6_addr_t *newp)
398{
399 u32_t sum;
400
401 sum = chksum_delta_32(oldp->addr[0], newp->addr[0]);
402 sum += chksum_delta_32(oldp->addr[1], newp->addr[1]);
403 sum += chksum_delta_32(oldp->addr[2], newp->addr[2]);
404 sum += chksum_delta_32(oldp->addr[3], newp->addr[3]);
405
406 return sum;
407}
408
409
410static u32_t
411chksum_update_ipv6(ip6_addr_t *oldp, const ip6_addr_t *newp)
412{
413 u32_t sum;
414
415 sum = chksum_update_32(&oldp->addr[0], newp->addr[0]);
416 sum += chksum_update_32(&oldp->addr[1], newp->addr[1]);
417 sum += chksum_update_32(&oldp->addr[2], newp->addr[2]);
418 sum += chksum_update_32(&oldp->addr[3], newp->addr[3]);
419
420 return sum;
421}
422
423
424/**
425 * ICMP Echo Request in pbuf "p" is to be proxied.
426 */
427static void
428pxping_recv4(void *arg, struct pbuf *p)
429{
430 struct pxping *pxping = (struct pxping *)arg;
431 struct ping_pcb *pcb;
432#ifdef DF_WITH_IP_HDRINCL
433 struct ip_hdr iph_orig;
434#endif
435 struct icmp_echo_hdr icmph_orig;
436 struct ip_hdr *iph;
437 struct icmp_echo_hdr *icmph;
438 int df, ttl, tos;
439 u32_t sum;
440 u16_t iphlen;
441 int status;
442
443 iphlen = ip_current_header_tot_len();
444 if (iphlen != IP_HLEN) { /* we don't do options */
445 pbuf_free(p);
446 return;
447 }
448
449 iph = (/* UNCONST */ struct ip_hdr *)ip_current_header();
450 icmph = (struct icmp_echo_hdr *)p->payload;
451
452 pcb = pxping_pcb_for_request(pxping, 0,
453 ipX_current_src_addr(),
454 ipX_current_dest_addr(),
455 icmph->id);
456 if (pcb == NULL) {
457 pbuf_free(p);
458 return;
459 }
460
461 DPRINTF(("ping %p: %R[ping_pcb] seq %d len %u ttl %d\n",
462 pcb, pcb,
463 ntohs(icmph->seqno), (unsigned int)p->tot_len,
464 IPH_TTL(iph)));
465
466 ttl = IPH_TTL(iph);
467 if (!pcb->is_mapped) {
468 if (RT_UNLIKELY(ttl == 1)) {
469 status = pbuf_header(p, iphlen); /* back to IP header */
470 if (RT_LIKELY(status == 0)) {
471 icmp_time_exceeded(p, ICMP_TE_TTL);
472 }
473 pbuf_free(p);
474 return;
475 }
476 --ttl;
477 }
478
479 /*
480 * OS X doesn't provide a socket option to control fragmentation.
481 * Solaris doesn't provide IP_DONTFRAG on all releases we support.
482 * In this case we have to use IP_HDRINCL. We don't want to use
483 * it always since it doesn't handle fragmentation (but that's ok
484 * for DF) and Windows doesn't do automatic source address
485 * selection with IP_HDRINCL.
486 */
487 df = (IPH_OFFSET(iph) & PP_HTONS(IP_DF)) != 0;
488
489#ifdef DF_WITH_IP_HDRINCL
490 if (df != pxping->hdrincl) {
491 status = setsockopt(pxping->sock4, IPPROTO_IP, IP_HDRINCL,
492 &df, sizeof(df));
493 if (RT_LIKELY(status == 0)) {
494 pxping->hdrincl = df;
495 }
496 else {
497 DPRINTF(("IP_HDRINCL: %R[sockerr]\n", SOCKERRNO()));
498 }
499 }
500
501 if (pxping->hdrincl) {
502 status = pbuf_header(p, iphlen); /* back to IP header */
503 if (RT_UNLIKELY(status != 0)) {
504 pbuf_free(p);
505 return;
506 }
507
508 /* we will overwrite IP header, save original for ICMP errors */
509 memcpy(&iph_orig, iph, iphlen);
510
511 if (pcb->is_mapped) {
512 ip4_addr_set_u32(&iph->dest, pcb->peer.sin.sin_addr.s_addr);
513 }
514
515 if (g_proxy_options->src4 != NULL) {
516 memcpy(&iph->src, &g_proxy_options->src4->sin_addr,
517 sizeof(g_proxy_options->src4->sin_addr));
518 }
519 else {
520 /* let the kernel select suitable source address */
521 memset(&iph->src, 0, sizeof(iph->src));
522 }
523
524 IPH_TTL_SET(iph, ttl); /* already decremented */
525 IPH_ID_SET(iph, 0); /* kernel will set one */
526#ifdef RT_OS_DARWIN
527 /* wants ip_offset and ip_len fields in host order */
528 IPH_OFFSET_SET(iph, ntohs(IPH_OFFSET(iph)));
529 IPH_LEN_SET(iph, ntohs(IPH_LEN(iph)));
530 /* wants checksum of everything (sic!), in host order */
531 sum = inet_chksum_pbuf(p);
532 IPH_CHKSUM_SET(iph, sum);
533#else /* !RT_OS_DARWIN */
534 IPH_CHKSUM_SET(iph, 0); /* kernel will recalculate */
535#endif
536 }
537 else /* !pxping->hdrincl */
538#endif /* DF_WITH_IP_HDRINCL */
539 {
540#if !defined(DF_WITH_IP_HDRINCL)
541 /* control DF flag via setsockopt(2) */
542#define USE_DF_OPTION(_Optname) \
543 const int dfopt = _Optname; \
544 const char * const dfoptname = #_Optname;
545#if defined(RT_OS_LINUX)
546 USE_DF_OPTION(IP_MTU_DISCOVER);
547 df = df ? IP_PMTUDISC_DO : IP_PMTUDISC_DONT;
548#elif defined(RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
549 USE_DF_OPTION(IP_DONTFRAG);
550#elif defined(RT_OS_WINDOWS)
551 USE_DF_OPTION(IP_DONTFRAGMENT);
552#endif
553 if (df != pxping->df) {
554 status = setsockopt(pxping->sock4, IPPROTO_IP, dfopt,
555 (char *)&df, sizeof(df));
556 if (RT_LIKELY(status == 0)) {
557 pxping->df = df;
558 }
559 else {
560 DPRINTF(("%s: %R[sockerr]\n", dfoptname, SOCKERRNO()));
561 }
562 }
563#endif /* !DF_WITH_IP_HDRINCL */
564
565 if (ttl != pxping->ttl) {
566 status = setsockopt(pxping->sock4, IPPROTO_IP, IP_TTL,
567 (char *)&ttl, sizeof(ttl));
568 if (RT_LIKELY(status == 0)) {
569 pxping->ttl = ttl;
570 }
571 else {
572 DPRINTF(("IP_TTL: %R[sockerr]\n", SOCKERRNO()));
573 }
574 }
575
576 tos = IPH_TOS(iph);
577 if (tos != pxping->tos) {
578 status = setsockopt(pxping->sock4, IPPROTO_IP, IP_TOS,
579 (char *)&tos, sizeof(tos));
580 if (RT_LIKELY(status == 0)) {
581 pxping->tos = tos;
582 }
583 else {
584 DPRINTF(("IP_TOS: %R[sockerr]\n", SOCKERRNO()));
585 }
586 }
587 }
588
589 /* rewrite ICMP echo header */
590 memcpy(&icmph_orig, icmph, sizeof(*icmph));
591 sum = (u16_t)~icmph->chksum;
592 sum += chksum_update_16(&icmph->id, pcb->host_id);
593 sum = FOLD_U32T(sum);
594 icmph->chksum = ~sum;
595
596 status = proxy_sendto(pxping->sock4, p,
597 &pcb->peer.sin, sizeof(pcb->peer.sin));
598 if (status != 0) {
599 int error = -status;
600 DPRINTF(("%s: sendto: %R[sockerr]\n", __func__, error));
601
602#ifdef DF_WITH_IP_HDRINCL
603 if (pxping->hdrincl) {
604 /* restore original IP header */
605 memcpy(iph, &iph_orig, iphlen);
606 }
607 else
608#endif
609 {
610 status = pbuf_header(p, iphlen); /* back to IP header */
611 if (RT_UNLIKELY(status != 0)) {
612 pbuf_free(p);
613 return;
614 }
615 }
616
617 /* restore original ICMP header */
618 memcpy(icmph, &icmph_orig, sizeof(*icmph));
619
620 /*
621 * Some ICMP errors may be generated by the kernel and we read
622 * them from the socket and forward them normally, hence the
623 * ifdefs below.
624 */
625 switch (error) {
626
627#if !( defined(RT_OS_SOLARIS) \
628 || (defined(RT_OS_LINUX) && !defined(DF_WITH_IP_HDRINCL)) \
629 )
630 case EMSGSIZE:
631 icmp_dest_unreach(p, ICMP_DUR_FRAG);
632 break;
633#endif
634
635 case ENETDOWN:
636 case ENETUNREACH:
637 icmp_dest_unreach(p, ICMP_DUR_NET);
638 break;
639
640 case EHOSTDOWN:
641 case EHOSTUNREACH:
642 icmp_dest_unreach(p, ICMP_DUR_HOST);
643 break;
644 }
645 }
646
647 pbuf_free(p);
648}
649
650
651/**
652 * ICMPv6 Echo Request in pbuf "p" is to be proxied.
653 */
654static void
655pxping_recv6(void *arg, struct pbuf *p)
656{
657 struct pxping *pxping = (struct pxping *)arg;
658 struct ping_pcb *pcb;
659 struct ip6_hdr *iph;
660 struct icmp6_echo_hdr *icmph;
661 int hopl;
662 u16_t iphlen;
663 u16_t id, seq;
664 int status;
665
666 iph = (/* UNCONST */ struct ip6_hdr *)ip6_current_header();
667 iphlen = ip_current_header_tot_len();
668
669 icmph = (struct icmp6_echo_hdr *)p->payload;
670
671 id = icmph->id;
672 seq = icmph->seqno;
673
674 pcb = pxping_pcb_for_request(pxping, 1,
675 ipX_current_src_addr(),
676 ipX_current_dest_addr(),
677 id);
678 if (pcb == NULL) {
679 pbuf_free(p);
680 return;
681 }
682
683 DPRINTF(("ping %p: %R[ping_pcb] seq %d len %u hopl %d\n",
684 pcb, pcb,
685 ntohs(seq), (unsigned int)p->tot_len,
686 IP6H_HOPLIM(iph)));
687
688 hopl = IP6H_HOPLIM(iph);
689 if (!pcb->is_mapped) {
690 if (hopl == 1) {
691 status = pbuf_header(p, iphlen); /* back to IP header */
692 if (RT_LIKELY(status == 0)) {
693 icmp6_time_exceeded(p, ICMP6_TE_HL);
694 }
695 pbuf_free(p);
696 return;
697 }
698 --hopl;
699 }
700
701 /*
702 * Rewrite ICMPv6 echo header. We don't need to recompute the
703 * checksum since, unlike IPv4, checksum includes pseudo-header.
704 * OS computes checksum for us on send() since it needs to select
705 * source address.
706 */
707 icmph->id = pcb->host_id;
708
709 /* TODO: use control messages to save a syscall? */
710 if (hopl != pxping->hopl) {
711 status = setsockopt(pxping->sock6, IPPROTO_IPV6, IPV6_UNICAST_HOPS,
712 (char *)&hopl, sizeof(hopl));
713 if (status == 0) {
714 pxping->hopl = hopl;
715 }
716 else {
717 DPRINTF(("IPV6_HOPLIMIT: %R[sockerr]\n", SOCKERRNO()));
718 }
719 }
720
721 status = proxy_sendto(pxping->sock6, p,
722 &pcb->peer.sin6, sizeof(pcb->peer.sin6));
723 if (status != 0) {
724 int error = -status;
725 DPRINTF(("%s: sendto: %R[sockerr]\n", __func__, error));
726
727 status = pbuf_header(p, iphlen); /* back to IP header */
728 if (RT_UNLIKELY(status != 0)) {
729 pbuf_free(p);
730 return;
731 }
732
733 /* restore original ICMP header */
734 icmph->id = pcb->guest_id;
735
736 switch (error) {
737 case EACCES:
738 icmp6_dest_unreach(p, ICMP6_DUR_PROHIBITED);
739 break;
740
741#ifdef ENONET
742 case ENONET:
743#endif
744 case ENETDOWN:
745 case ENETUNREACH:
746 case EHOSTDOWN:
747 case EHOSTUNREACH:
748 icmp6_dest_unreach(p, ICMP6_DUR_NO_ROUTE);
749 break;
750 }
751 }
752
753 pbuf_free(p);
754}
755
756
757/**
758 * Formatter for %R[ping_pcb].
759 */
760static DECLCALLBACK(size_t)
761pxping_pcb_rtstrfmt(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput,
762 const char *pszType, const void *pvValue,
763 int cchWidth, int cchPrecision, unsigned int fFlags,
764 void *pvUser)
765{
766 const struct ping_pcb *pcb = (const struct ping_pcb *)pvValue;
767 size_t cb = 0;
768
769 NOREF(cchWidth);
770 NOREF(cchPrecision);
771 NOREF(fFlags);
772 NOREF(pvUser);
773
774 AssertReturn(strcmp(pszType, "ping_pcb") == 0, 0);
775
776 if (pcb == NULL) {
777 return RTStrFormat(pfnOutput, pvArgOutput, NULL, NULL, "(null)");
778 }
779
780 /* XXX: %RTnaipv4 takes the value, but %RTnaipv6 takes the pointer */
781 if (pcb->is_ipv6) {
782 cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, NULL,
783 "%RTnaipv6 -> %RTnaipv6", &pcb->src, &pcb->dst);
784 if (pcb->is_mapped) {
785 cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, NULL,
786 " (%RTnaipv6)", &pcb->peer.sin6.sin6_addr);
787 }
788 }
789 else {
790 cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, NULL,
791 "%RTnaipv4 -> %RTnaipv4",
792 ip4_addr_get_u32(ipX_2_ip(&pcb->src)),
793 ip4_addr_get_u32(ipX_2_ip(&pcb->dst)));
794 if (pcb->is_mapped) {
795 cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, NULL,
796 " (%RTnaipv4)", pcb->peer.sin.sin_addr.s_addr);
797 }
798 }
799
800 cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, NULL,
801 " id %04x->%04x", ntohs(pcb->guest_id), ntohs(pcb->host_id));
802
803 return cb;
804}
805
806
807static struct ping_pcb *
808pxping_pcb_allocate(struct pxping *pxping)
809{
810 struct ping_pcb *pcb;
811
812 if (pxping->npcbs >= PXPING_MAX_PCBS) {
813 return NULL;
814 }
815
816 pcb = (struct ping_pcb *)malloc(sizeof(*pcb));
817 if (pcb == NULL) {
818 return NULL;
819 }
820
821 ++pxping->npcbs;
822 return pcb;
823}
824
825
826static void
827pxping_pcb_delete(struct pxping *pxping, struct ping_pcb *pcb)
828{
829 LWIP_ASSERT1(pxping->npcbs > 0);
830 LWIP_ASSERT1(pcb->next == NULL);
831 LWIP_ASSERT1(pcb->pprev_timeout == NULL);
832
833 DPRINTF(("%s: ping %p\n", __func__, (void *)pcb));
834
835 --pxping->npcbs;
836 free(pcb);
837}
838
839
840static void
841pxping_timeout_add(struct pxping *pxping, struct ping_pcb *pcb)
842{
843 struct ping_pcb **chain;
844
845 LWIP_ASSERT1(pcb->pprev_timeout == NULL);
846
847 chain = &pxping->timeout_list[pcb->timeout_slot];
848 if ((pcb->next_timeout = *chain) != NULL) {
849 (*chain)->pprev_timeout = &pcb->next_timeout;
850 }
851 *chain = pcb;
852 pcb->pprev_timeout = chain;
853}
854
855
856static void
857pxping_timeout_del(struct pxping *pxping, struct ping_pcb *pcb)
858{
859 LWIP_UNUSED_ARG(pxping);
860
861 LWIP_ASSERT1(pcb->pprev_timeout != NULL);
862 if (pcb->next_timeout != NULL) {
863 pcb->next_timeout->pprev_timeout = pcb->pprev_timeout;
864 }
865 *pcb->pprev_timeout = pcb->next_timeout;
866 pcb->pprev_timeout = NULL;
867 pcb->next_timeout = NULL;
868}
869
870
871static void
872pxping_pcb_register(struct pxping *pxping, struct ping_pcb *pcb)
873{
874 pcb->next = pxping->pcbs;
875 pxping->pcbs = pcb;
876
877 pxping_timeout_add(pxping, pcb);
878}
879
880
881static void
882pxping_pcb_deregister(struct pxping *pxping, struct ping_pcb *pcb)
883{
884 struct ping_pcb **p;
885
886 for (p = &pxping->pcbs; *p != NULL; p = &(*p)->next) {
887 if (*p == pcb) {
888 *p = pcb->next;
889 pcb->next = NULL;
890 break;
891 }
892 }
893
894 pxping_timeout_del(pxping, pcb);
895}
896
897
898static struct ping_pcb *
899pxping_pcb_for_request(struct pxping *pxping,
900 int is_ipv6, ipX_addr_t *src, ipX_addr_t *dst,
901 u16_t guest_id)
902{
903 struct ping_pcb *pcb;
904
905 /* on lwip thread, so no concurrent updates */
906 for (pcb = pxping->pcbs; pcb != NULL; pcb = pcb->next) {
907 if (pcb->guest_id == guest_id
908 && pcb->is_ipv6 == is_ipv6
909 && ipX_addr_cmp(is_ipv6, &pcb->dst, dst)
910 && ipX_addr_cmp(is_ipv6, &pcb->src, src))
911 {
912 break;
913 }
914 }
915
916 if (pcb == NULL) {
917 int mapped;
918
919 pcb = pxping_pcb_allocate(pxping);
920 if (pcb == NULL) {
921 return NULL;
922 }
923
924 pcb->is_ipv6 = is_ipv6;
925 ipX_addr_copy(is_ipv6, pcb->src, *src);
926 ipX_addr_copy(is_ipv6, pcb->dst, *dst);
927
928 pcb->guest_id = guest_id;
929#ifdef RT_OS_WINDOWS
930# define random() (rand())
931#endif
932 pcb->host_id = random() & 0xffffUL;
933
934 pcb->pprev_timeout = NULL;
935 pcb->next_timeout = NULL;
936
937 if (is_ipv6) {
938 pcb->peer.sin6.sin6_family = AF_INET6;
939#if HAVE_SA_LEN
940 pcb->peer.sin6.sin6_len = sizeof(pcb->peer.sin6);
941#endif
942 pcb->peer.sin6.sin6_port = htons(IPPROTO_ICMPV6);
943 pcb->peer.sin6.sin6_flowinfo = 0;
944 mapped = pxremap_outbound_ip6((ip6_addr_t *)&pcb->peer.sin6.sin6_addr,
945 ipX_2_ip6(&pcb->dst));
946 }
947 else {
948 pcb->peer.sin.sin_family = AF_INET;
949#if HAVE_SA_LEN
950 pcb->peer.sin.sin_len = sizeof(pcb->peer.sin);
951#endif
952 pcb->peer.sin.sin_port = htons(IPPROTO_ICMP);
953 mapped = pxremap_outbound_ip4((ip_addr_t *)&pcb->peer.sin.sin_addr,
954 ipX_2_ip(&pcb->dst));
955 }
956
957 if (mapped == PXREMAP_FAILED) {
958 free(pcb);
959 return NULL;
960 }
961 else {
962 pcb->is_mapped = (mapped == PXREMAP_MAPPED);
963 }
964
965 pcb->timeout_slot = pxping->timeout_slot;
966
967 sys_mutex_lock(&pxping->lock);
968 pxping_pcb_register(pxping, pcb);
969 sys_mutex_unlock(&pxping->lock);
970
971 DPRINTF(("ping %p: %R[ping_pcb] - created\n", pcb, pcb));
972
973 pxping_timer_needed(pxping);
974 }
975 else {
976 /* just bump up expiration timeout lazily */
977 DPRINTF(("ping %p: %R[ping_pcb] - slot %d -> %d\n",
978 pcb, pcb,
979 (unsigned int)pcb->timeout_slot,
980 (unsigned int)pxping->timeout_slot));
981 pcb->timeout_slot = pxping->timeout_slot;
982 }
983
984 return pcb;
985}
986
987
988/**
989 * Called on pollmgr thread. Caller must do the locking since caller
990 * is going to use the returned pcb, which needs to be protected from
991 * being expired by pxping_timer() on lwip thread.
992 */
993static struct ping_pcb *
994pxping_pcb_for_reply(struct pxping *pxping,
995 int is_ipv6, ipX_addr_t *dst, u16_t host_id)
996{
997 struct ping_pcb *pcb;
998
999 for (pcb = pxping->pcbs; pcb != NULL; pcb = pcb->next) {
1000 if (pcb->host_id == host_id
1001 && pcb->is_ipv6 == is_ipv6
1002 /* XXX: allow broadcast pings? */
1003 && ipX_addr_cmp(is_ipv6, &pcb->dst, dst))
1004 {
1005 return pcb;
1006 }
1007 }
1008
1009 return NULL;
1010}
1011
1012
1013static void
1014pxping_timer(void *arg)
1015{
1016 struct pxping *pxping = (struct pxping *)arg;
1017 struct ping_pcb **chain, *pcb;
1018
1019 pxping->timer_active = 0;
1020
1021 /*
1022 * New slot points to the list of pcbs to check for expiration.
1023 */
1024 LWIP_ASSERT1(pxping->timeout_slot < TIMEOUT);
1025 if (++pxping->timeout_slot == TIMEOUT) {
1026 pxping->timeout_slot = 0;
1027 }
1028
1029 chain = &pxping->timeout_list[pxping->timeout_slot];
1030 pcb = *chain;
1031
1032 /* protect from pollmgr concurrent reads */
1033 sys_mutex_lock(&pxping->lock);
1034
1035 while (pcb != NULL) {
1036 struct ping_pcb *xpcb = pcb;
1037 pcb = pcb->next_timeout;
1038
1039 if (xpcb->timeout_slot == pxping->timeout_slot) {
1040 /* expired */
1041 pxping_pcb_deregister(pxping, xpcb);
1042 pxping_pcb_delete(pxping, xpcb);
1043 }
1044 else {
1045 /*
1046 * If there was another request, we updated timeout_slot
1047 * but delayed actually moving the pcb until now.
1048 */
1049 pxping_timeout_del(pxping, xpcb); /* from current slot */
1050 pxping_timeout_add(pxping, xpcb); /* to new slot */
1051 }
1052 }
1053
1054 sys_mutex_unlock(&pxping->lock);
1055 pxping_timer_needed(pxping);
1056}
1057
1058
1059static void
1060pxping_timer_needed(struct pxping *pxping)
1061{
1062 if (!pxping->timer_active && pxping->pcbs != NULL) {
1063 pxping->timer_active = 1;
1064 sys_timeout(1 * 1000, pxping_timer, pxping);
1065 }
1066}
1067
1068
1069static int
1070pxping_pmgr_pump(struct pollmgr_handler *handler, SOCKET fd, int revents)
1071{
1072 struct pxping *pxping;
1073
1074 pxping = (struct pxping *)handler->data;
1075 LWIP_ASSERT1(fd == pxping->sock4 || fd == pxping->sock6);
1076
1077 if (revents & ~(POLLIN|POLLERR)) {
1078 DPRINTF0(("%s: unexpected revents 0x%x\n", __func__, revents));
1079 return POLLIN;
1080 }
1081
1082 if (revents & POLLERR) {
1083 int sockerr = -1;
1084 socklen_t optlen = (socklen_t)sizeof(sockerr);
1085 int status;
1086
1087 status = getsockopt(fd, SOL_SOCKET,
1088 SO_ERROR, (char *)&sockerr, &optlen);
1089 if (status < 0) {
1090 DPRINTF(("%s: sock %d: SO_ERROR failed: %R[sockerr]\n",
1091 __func__, fd, SOCKERRNO()));
1092 }
1093 else {
1094 DPRINTF(("%s: sock %d: %R[sockerr]\n",
1095 __func__, fd, sockerr));
1096 }
1097 }
1098
1099 if ((revents & POLLIN) == 0) {
1100 return POLLIN;
1101 }
1102
1103 if (fd == pxping->sock4) {
1104 pxping_pmgr_icmp4(pxping);
1105 }
1106 else /* fd == pxping->sock6 */ {
1107 pxping_pmgr_icmp6(pxping);
1108 }
1109
1110 return POLLIN;
1111}
1112
1113
1114/**
1115 * Process incoming ICMP message for the host.
1116 * NB: we will get a lot of spam here and have to sift through it.
1117 */
1118static void
1119pxping_pmgr_icmp4(struct pxping *pxping)
1120{
1121 struct sockaddr_in sin;
1122 socklen_t salen = sizeof(sin);
1123 ssize_t nread;
1124 struct ip_hdr *iph;
1125 struct icmp_echo_hdr *icmph;
1126 u16_t iplen, ipoff;
1127
1128 memset(&sin, 0, sizeof(sin));
1129
1130 /*
1131 * Reads from raw IPv4 sockets deliver complete IP datagrams with
1132 * IP header included.
1133 */
1134 nread = recvfrom(pxping->sock4, pollmgr_udpbuf, sizeof(pollmgr_udpbuf), 0,
1135 (struct sockaddr *)&sin, &salen);
1136 if (nread < 0) {
1137 DPRINTF(("%s: %R[sockerr]\n", __func__, SOCKERRNO()));
1138 return;
1139 }
1140
1141 if (nread < IP_HLEN) {
1142 DPRINTF2(("%s: read %d bytes, IP header truncated\n",
1143 __func__, (unsigned int)nread));
1144 return;
1145 }
1146
1147 iph = (struct ip_hdr *)pollmgr_udpbuf;
1148
1149 /* match version */
1150 if (IPH_V(iph) != 4) {
1151 DPRINTF2(("%s: unexpected IP version %d\n", __func__, IPH_V(iph)));
1152 return;
1153 }
1154
1155 /* no fragmentation */
1156 ipoff = IPH_OFFSET(iph);
1157#if defined(RT_OS_DARWIN)
1158 /* darwin reports IPH_OFFSET in host byte order */
1159 ipoff = htons(ipoff);
1160 IPH_OFFSET_SET(iph, ipoff);
1161#endif
1162 if ((ipoff & PP_HTONS(IP_OFFMASK | IP_MF)) != 0) {
1163 DPRINTF2(("%s: dropping fragmented datagram (0x%04x)\n",
1164 __func__, ntohs(ipoff)));
1165 return;
1166 }
1167
1168 /* no options */
1169 if (IPH_HL(iph) * 4 != IP_HLEN) {
1170 DPRINTF2(("%s: dropping datagram with options (IP header length %d)\n",
1171 __func__, IPH_HL(iph) * 4));
1172 return;
1173 }
1174
1175 if (IPH_PROTO(iph) != IP_PROTO_ICMP) {
1176 DPRINTF2(("%s: unexpected protocol %d\n", __func__, IPH_PROTO(iph)));
1177 return;
1178 }
1179
1180 iplen = IPH_LEN(iph);
1181#if !defined(RT_OS_DARWIN)
1182 /* darwin reports IPH_LEN in host byte order */
1183 iplen = ntohs(iplen);
1184#endif
1185#if defined(RT_OS_DARWIN) || defined(RT_OS_SOLARIS)
1186 /* darwin and solaris change IPH_LEN to payload length only */
1187 iplen += IP_HLEN; /* we verified there are no options */
1188 IPH_LEN_SET(iph, htons(iplen));
1189#endif
1190 if (nread < iplen) {
1191 DPRINTF2(("%s: read %d bytes but total length is %d bytes\n",
1192 __func__, (unsigned int)nread, (unsigned int)iplen));
1193 return;
1194 }
1195
1196 if (iplen < IP_HLEN + ICMP_HLEN) {
1197 DPRINTF2(("%s: IP length %d bytes, ICMP header truncated\n",
1198 __func__, iplen));
1199 return;
1200 }
1201
1202 icmph = (struct icmp_echo_hdr *)(pollmgr_udpbuf + IP_HLEN);
1203 if (ICMPH_TYPE(icmph) == ICMP_ER) {
1204 pxping_pmgr_icmp4_echo(pxping, iplen, &sin);
1205 }
1206 else if (ICMPH_TYPE(icmph) == ICMP_DUR || ICMPH_TYPE(icmph) == ICMP_TE) {
1207 pxping_pmgr_icmp4_error(pxping, iplen, &sin);
1208 }
1209#if 1
1210 else {
1211 DPRINTF2(("%s: ignoring ICMP type %d\n", __func__, ICMPH_TYPE(icmph)));
1212 }
1213#endif
1214}
1215
1216
1217/**
1218 * Check if this incoming ICMP echo reply is for one of our pings and
1219 * forward it to the guest.
1220 */
1221static void
1222pxping_pmgr_icmp4_echo(struct pxping *pxping,
1223 u16_t iplen, struct sockaddr_in *peer)
1224{
1225 struct ip_hdr *iph;
1226 struct icmp_echo_hdr *icmph;
1227 u16_t id, seq;
1228 ip_addr_t guest_ip, target_ip;
1229 int mapped;
1230 struct ping_pcb *pcb;
1231 u16_t guest_id;
1232 u16_t oipsum;
1233 u32_t sum;
1234
1235 iph = (struct ip_hdr *)pollmgr_udpbuf;
1236 icmph = (struct icmp_echo_hdr *)(pollmgr_udpbuf + IP_HLEN);
1237
1238 id = icmph->id;
1239 seq = icmph->seqno;
1240
1241 DPRINTF(("<--- PING %RTnaipv4 id 0x%x seq %d\n",
1242 peer->sin_addr.s_addr, ntohs(id), ntohs(seq)));
1243
1244 /*
1245 * Is this a reply to one of our pings?
1246 */
1247
1248 ip_addr_copy(target_ip, iph->src);
1249 mapped = pxremap_inbound_ip4(&target_ip, &target_ip);
1250 if (mapped == PXREMAP_FAILED) {
1251 return;
1252 }
1253 if (mapped == PXREMAP_ASIS && IPH_TTL(iph) == 1) {
1254 DPRINTF2(("%s: dropping packet with ttl 1\n", __func__));
1255 return;
1256 }
1257
1258 sys_mutex_lock(&pxping->lock);
1259 pcb = pxping_pcb_for_reply(pxping, 0, ip_2_ipX(&target_ip), id);
1260 if (pcb == NULL) {
1261 sys_mutex_unlock(&pxping->lock);
1262 DPRINTF2(("%s: no match\n", __func__));
1263 return;
1264 }
1265
1266 DPRINTF2(("%s: pcb %p\n", __func__, (void *)pcb));
1267
1268 /* save info before unlocking since pcb may expire */
1269 ip_addr_copy(guest_ip, *ipX_2_ip(&pcb->src));
1270 guest_id = pcb->guest_id;
1271
1272 sys_mutex_unlock(&pxping->lock);
1273
1274
1275 /*
1276 * Rewrite headers and forward to guest.
1277 */
1278
1279 /* rewrite ICMP echo header */
1280 sum = (u16_t)~icmph->chksum;
1281 sum += chksum_update_16(&icmph->id, guest_id);
1282 sum = FOLD_U32T(sum);
1283 icmph->chksum = ~sum;
1284
1285 /* rewrite IP header */
1286 oipsum = IPH_CHKSUM(iph);
1287 if (oipsum == 0) {
1288 /* Solaris doesn't compute checksum for local replies */
1289 ip_addr_copy(iph->dest, guest_ip);
1290 if (mapped == PXREMAP_MAPPED) {
1291 ip_addr_copy(iph->src, target_ip);
1292 }
1293 else {
1294 IPH_TTL_SET(iph, IPH_TTL(iph) - 1);
1295 }
1296 IPH_CHKSUM_SET(iph, inet_chksum(iph, ntohs(IPH_LEN(iph))));
1297 }
1298 else {
1299 sum = (u16_t)~oipsum;
1300 sum += chksum_update_32((u32_t *)&iph->dest,
1301 ip4_addr_get_u32(&guest_ip));
1302 if (mapped == PXREMAP_MAPPED) {
1303 sum += chksum_update_32((u32_t *)&iph->src,
1304 ip4_addr_get_u32(&target_ip));
1305 }
1306 else {
1307 IPH_TTL_SET(iph, IPH_TTL(iph) - 1);
1308 sum += PP_NTOHS(~0x0100);
1309 }
1310 sum = FOLD_U32T(sum);
1311 IPH_CHKSUM_SET(iph, ~sum);
1312 }
1313
1314 pxping_pmgr_forward_inbound(pxping, iplen);
1315}
1316
1317
1318/**
1319 * Check if this incoming ICMP error (destination unreachable or time
1320 * exceeded) is about one of our pings and forward it to the guest.
1321 */
1322static void
1323pxping_pmgr_icmp4_error(struct pxping *pxping,
1324 u16_t iplen, struct sockaddr_in *peer)
1325{
1326 struct ip_hdr *iph, *oiph;
1327 struct icmp_echo_hdr *icmph, *oicmph;
1328 u16_t oipoff, oiphlen, oiplen;
1329 u16_t id, seq;
1330 ip_addr_t guest_ip, target_ip, error_ip;
1331 int target_mapped, error_mapped;
1332 struct ping_pcb *pcb;
1333 u16_t guest_id;
1334 u32_t sum;
1335
1336 iph = (struct ip_hdr *)pollmgr_udpbuf;
1337 icmph = (struct icmp_echo_hdr *)(pollmgr_udpbuf + IP_HLEN);
1338
1339 /*
1340 * Inner IP datagram is not checked by the kernel and may be
1341 * anything, possibly malicious.
1342 */
1343
1344 oipoff = IP_HLEN + ICMP_HLEN;
1345 oiplen = iplen - oipoff; /* NB: truncated length, not IPH_LEN(oiph) */
1346 if (oiplen < IP_HLEN) {
1347 DPRINTF2(("%s: original datagram truncated to %d bytes\n",
1348 __func__, oiplen));
1349 }
1350
1351 /* IP header of the original message */
1352 oiph = (struct ip_hdr *)(pollmgr_udpbuf + oipoff);
1353
1354 /* match version */
1355 if (IPH_V(oiph) != 4) {
1356 DPRINTF2(("%s: unexpected IP version %d\n", __func__, IPH_V(oiph)));
1357 return;
1358 }
1359
1360 /* can't match fragments except the first one */
1361 if ((IPH_OFFSET(oiph) & PP_HTONS(IP_OFFMASK)) != 0) {
1362 DPRINTF2(("%s: ignoring fragment with offset %d\n",
1363 __func__, ntohs(IPH_OFFSET(oiph) & PP_HTONS(IP_OFFMASK))));
1364 return;
1365 }
1366
1367 if (IPH_PROTO(oiph) != IP_PROTO_ICMP) {
1368#if 0
1369 /* don't spam with every "destination unreachable" in the system */
1370 DPRINTF2(("%s: ignoring protocol %d\n", __func__, IPH_PROTO(oiph)));
1371#endif
1372 return;
1373 }
1374
1375 oiphlen = IPH_HL(oiph) * 4;
1376 if (oiplen < oiphlen + ICMP_HLEN) {
1377 DPRINTF2(("%s: original datagram truncated to %d bytes\n",
1378 __func__, oiplen));
1379 return;
1380 }
1381
1382 oicmph = (struct icmp_echo_hdr *)(pollmgr_udpbuf + oipoff + oiphlen);
1383 if (ICMPH_TYPE(oicmph) != ICMP_ECHO) {
1384 DPRINTF2(("%s: ignoring ICMP error for original ICMP type %d\n",
1385 __func__, ICMPH_TYPE(oicmph)));
1386 return;
1387 }
1388
1389 id = oicmph->id;
1390 seq = oicmph->seqno;
1391
1392 DPRINTF2(("%s: ping %RTnaipv4 id 0x%x seq %d",
1393 __func__, ip4_addr_get_u32(&oiph->dest), ntohs(id), ntohs(seq)));
1394 if (ICMPH_TYPE(icmph) == ICMP_DUR) {
1395 DPRINTF2((" unreachable (code %d)\n", ICMPH_CODE(icmph)));
1396 }
1397 else {
1398 DPRINTF2((" time exceeded\n"));
1399 }
1400
1401
1402 /*
1403 * Is the inner (failed) datagram one of our pings?
1404 */
1405
1406 ip_addr_copy(target_ip, oiph->dest); /* inner (failed) */
1407 target_mapped = pxremap_inbound_ip4(&target_ip, &target_ip);
1408 if (target_mapped == PXREMAP_FAILED) {
1409 return;
1410 }
1411
1412 sys_mutex_lock(&pxping->lock);
1413 pcb = pxping_pcb_for_reply(pxping, 0, ip_2_ipX(&target_ip), id);
1414 if (pcb == NULL) {
1415 sys_mutex_unlock(&pxping->lock);
1416 DPRINTF2(("%s: no match\n", __func__));
1417 return;
1418 }
1419
1420 DPRINTF2(("%s: pcb %p\n", __func__, (void *)pcb));
1421
1422 /* save info before unlocking since pcb may expire */
1423 ip_addr_copy(guest_ip, *ipX_2_ip(&pcb->src));
1424 guest_id = pcb->guest_id;
1425
1426 sys_mutex_unlock(&pxping->lock);
1427
1428
1429 /*
1430 * Rewrite both inner and outer headers and forward to guest.
1431 * Note that the checksum of the outer ICMP error message is
1432 * preserved by the changes we do to inner headers.
1433 */
1434
1435 ip_addr_copy(error_ip, iph->src); /* node that reports the error */
1436 error_mapped = pxremap_inbound_ip4(&error_ip, &error_ip);
1437 if (error_mapped == PXREMAP_FAILED) {
1438 return;
1439 }
1440 if (error_mapped == PXREMAP_ASIS && IPH_TTL(iph) == 1) {
1441 DPRINTF2(("%s: dropping packet with ttl 1\n", __func__));
1442 return;
1443 }
1444
1445 /* rewrite inner ICMP echo header */
1446 sum = (u16_t)~oicmph->chksum;
1447 sum += chksum_update_16(&oicmph->id, guest_id);
1448 sum = FOLD_U32T(sum);
1449 oicmph->chksum = ~sum;
1450
1451 /* rewrite inner IP header */
1452#if defined(RT_OS_DARWIN)
1453 /* darwin converts inner length to host byte order too */
1454 IPH_LEN_SET(oiph, htons(IPH_LEN(oiph)));
1455#endif
1456 sum = (u16_t)~IPH_CHKSUM(oiph);
1457 sum += chksum_update_32((u32_t *)&oiph->src, ip4_addr_get_u32(&guest_ip));
1458 if (target_mapped == PXREMAP_MAPPED) {
1459 sum += chksum_update_32((u32_t *)&oiph->dest, ip4_addr_get_u32(&target_ip));
1460 }
1461 sum = FOLD_U32T(sum);
1462 IPH_CHKSUM_SET(oiph, ~sum);
1463
1464 /* rewrite outer IP header */
1465 sum = (u16_t)~IPH_CHKSUM(iph);
1466 sum += chksum_update_32((u32_t *)&iph->dest, ip4_addr_get_u32(&guest_ip));
1467 if (error_mapped == PXREMAP_MAPPED) {
1468 sum += chksum_update_32((u32_t *)&iph->src, ip4_addr_get_u32(&error_ip));
1469 }
1470 else {
1471 IPH_TTL_SET(iph, IPH_TTL(iph) - 1);
1472 sum += PP_NTOHS(~0x0100);
1473 }
1474 sum = FOLD_U32T(sum);
1475 IPH_CHKSUM_SET(iph, ~sum);
1476
1477 pxping_pmgr_forward_inbound(pxping, iplen);
1478}
1479
1480
1481/**
1482 * Process incoming ICMPv6 message for the host.
1483 * NB: we will get a lot of spam here and have to sift through it.
1484 */
1485static void
1486pxping_pmgr_icmp6(struct pxping *pxping)
1487{
1488#ifndef RT_OS_WINDOWS
1489 struct msghdr mh;
1490 ssize_t nread;
1491#else
1492 WSAMSG mh;
1493 DWORD nread;
1494#endif
1495 IOVEC iov[1];
1496 static u8_t cmsgbuf[128];
1497 struct cmsghdr *cmh;
1498 struct sockaddr_in6 sin6;
1499 socklen_t salen = sizeof(sin6);
1500 struct icmp6_echo_hdr *icmph;
1501 struct in6_pktinfo *pktinfo;
1502 int hopl, tclass;
1503 int status;
1504
1505 /*
1506 * Reads from raw IPv6 sockets deliver only the payload. Full
1507 * headers are available via recvmsg(2)/cmsg(3).
1508 */
1509 IOVEC_SET_BASE(iov[0], pollmgr_udpbuf);
1510 IOVEC_SET_LEN(iov[0], sizeof(pollmgr_udpbuf));
1511
1512 memset(&mh, 0, sizeof(mh));
1513#ifndef RT_OS_WINDOWS
1514 mh.msg_name = &sin6;
1515 mh.msg_namelen = sizeof(sin6);
1516 mh.msg_iov = iov;
1517 mh.msg_iovlen = 1;
1518 mh.msg_control = cmsgbuf;
1519 mh.msg_controllen = sizeof(cmsgbuf);
1520 mh.msg_flags = 0;
1521
1522 nread = recvmsg(pxping->sock6, &mh, 0);
1523 if (nread < 0) {
1524 DPRINTF(("%s: %R[sockerr]\n", __func__, SOCKERRNO()));
1525 return;
1526 }
1527#else /* RT_OS_WINDOWS */
1528 mh.name = (LPSOCKADDR)&sin6;
1529 mh.namelen = sizeof(sin6);
1530 mh.lpBuffers = iov;
1531 mh.dwBufferCount = 1;
1532 mh.Control.buf = cmsgbuf;
1533 mh.Control.len = sizeof(cmsgbuf);
1534 mh.dwFlags = 0;
1535
1536 status = (*pxping->pfWSARecvMsg6)(pxping->sock6, &mh, &nread, NULL, NULL);
1537 if (status == SOCKET_ERROR) {
1538 DPRINTF2(("%s: error %d\n", __func__, WSAGetLastError()));
1539 return;
1540 }
1541#endif
1542
1543 icmph = (struct icmp6_echo_hdr *)pollmgr_udpbuf;
1544
1545 DPRINTF2(("%s: %RTnaipv6 ICMPv6: ", __func__, &sin6.sin6_addr));
1546
1547 if (icmph->type == ICMP6_TYPE_EREP) {
1548 DPRINTF2(("echo reply %04x %u\n",
1549 (unsigned int)icmph->id, (unsigned int)icmph->seqno));
1550 }
1551 else { /* XXX */
1552 if (icmph->type == ICMP6_TYPE_EREQ) {
1553 DPRINTF2(("echo request %04x %u\n",
1554 (unsigned int)icmph->id, (unsigned int)icmph->seqno));
1555 }
1556 else if (icmph->type == ICMP6_TYPE_DUR) {
1557 DPRINTF2(("destination unreachable\n"));
1558 }
1559 else if (icmph->type == ICMP6_TYPE_PTB) {
1560 DPRINTF2(("packet too big\n"));
1561 }
1562 else if (icmph->type == ICMP6_TYPE_TE) {
1563 DPRINTF2(("time exceeded\n"));
1564 }
1565 else if (icmph->type == ICMP6_TYPE_PP) {
1566 DPRINTF2(("parameter problem\n"));
1567 }
1568 else {
1569 DPRINTF2(("type %d len %u\n", icmph->type, (unsigned int)nread));
1570 }
1571
1572 if (icmph->type >= ICMP6_TYPE_EREQ) {
1573 return; /* informational message */
1574 }
1575 }
1576
1577 pktinfo = NULL;
1578 hopl = -1;
1579 tclass = -1;
1580 for (cmh = CMSG_FIRSTHDR(&mh); cmh != NULL; cmh = CMSG_NXTHDR(&mh, cmh)) {
1581 if (cmh->cmsg_len == 0)
1582 break;
1583
1584 if (cmh->cmsg_level == IPPROTO_IPV6
1585 && cmh->cmsg_type == IPV6_HOPLIMIT
1586 && cmh->cmsg_len == CMSG_LEN(sizeof(int)))
1587 {
1588 hopl = *(int *)CMSG_DATA(cmh);
1589 DPRINTF2(("hoplimit = %d\n", hopl));
1590 }
1591
1592 if (cmh->cmsg_level == IPPROTO_IPV6
1593 && cmh->cmsg_type == IPV6_PKTINFO
1594 && cmh->cmsg_len == CMSG_LEN(sizeof(struct in6_pktinfo)))
1595 {
1596 pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmh);
1597 DPRINTF2(("pktinfo found\n"));
1598 }
1599 }
1600
1601 if (pktinfo == NULL) {
1602 /*
1603 * ip6_output_if() doesn't do checksum for us so we need to
1604 * manually recompute it - for this we must know the
1605 * destination address of the pseudo-header that we will
1606 * rewrite with guest's address. (TODO: yeah, yeah, we can
1607 * compute it from scratch...)
1608 */
1609 DPRINTF2(("%s: unable to get pktinfo\n", __func__));
1610 return;
1611 }
1612
1613 if (hopl < 0) {
1614 hopl = LWIP_ICMP6_HL;
1615 }
1616
1617 if (icmph->type == ICMP6_TYPE_EREP) {
1618 pxping_pmgr_icmp6_echo(pxping,
1619 (ip6_addr_t *)&sin6.sin6_addr,
1620 (ip6_addr_t *)&pktinfo->ipi6_addr,
1621 hopl, tclass, (u16_t)nread);
1622 }
1623 else if (icmph->type < ICMP6_TYPE_EREQ) {
1624 pxping_pmgr_icmp6_error(pxping,
1625 (ip6_addr_t *)&sin6.sin6_addr,
1626 (ip6_addr_t *)&pktinfo->ipi6_addr,
1627 hopl, tclass, (u16_t)nread);
1628 }
1629}
1630
1631
1632/**
1633 * Check if this incoming ICMPv6 echo reply is for one of our pings
1634 * and forward it to the guest.
1635 */
1636static void
1637pxping_pmgr_icmp6_echo(struct pxping *pxping,
1638 ip6_addr_t *src, ip6_addr_t *dst,
1639 int hopl, int tclass, u16_t icmplen)
1640{
1641 struct icmp6_echo_hdr *icmph;
1642 ip6_addr_t guest_ip, target_ip;
1643 int mapped;
1644 struct ping_pcb *pcb;
1645 u16_t id, guest_id;
1646 u32_t sum;
1647
1648 ip6_addr_copy(target_ip, *src);
1649 mapped = pxremap_inbound_ip6(&target_ip, &target_ip);
1650 if (mapped == PXREMAP_FAILED) {
1651 return;
1652 }
1653 else if (mapped == PXREMAP_ASIS) {
1654 if (hopl == 1) {
1655 DPRINTF2(("%s: dropping packet with ttl 1\n", __func__));
1656 return;
1657 }
1658 --hopl;
1659 }
1660
1661 icmph = (struct icmp6_echo_hdr *)pollmgr_udpbuf;
1662 id = icmph->id;
1663
1664 sys_mutex_lock(&pxping->lock);
1665 pcb = pxping_pcb_for_reply(pxping, 1, ip6_2_ipX(&target_ip), id);
1666 if (pcb == NULL) {
1667 sys_mutex_unlock(&pxping->lock);
1668 DPRINTF2(("%s: no match\n", __func__));
1669 return;
1670 }
1671
1672 DPRINTF2(("%s: pcb %p\n", __func__, (void *)pcb));
1673
1674 /* save info before unlocking since pcb may expire */
1675 ip6_addr_copy(guest_ip, *ipX_2_ip6(&pcb->src));
1676 guest_id = pcb->guest_id;
1677
1678 sys_mutex_unlock(&pxping->lock);
1679
1680 /* rewrite ICMPv6 echo header */
1681 sum = (u16_t)~icmph->chksum;
1682 sum += chksum_update_16(&icmph->id, guest_id);
1683 sum += chksum_delta_ipv6(dst, &guest_ip); /* pseudo */
1684 if (mapped) {
1685 sum += chksum_delta_ipv6(src, &target_ip); /* pseudo */
1686 }
1687 sum = FOLD_U32T(sum);
1688 icmph->chksum = ~sum;
1689
1690 pxping_pmgr_forward_inbound6(pxping,
1691 &target_ip, /* echo reply src */
1692 &guest_ip, /* echo reply dst */
1693 hopl, tclass, icmplen);
1694}
1695
1696
1697/**
1698 * Check if this incoming ICMPv6 error is about one of our pings and
1699 * forward it to the guest.
1700 */
1701static void
1702pxping_pmgr_icmp6_error(struct pxping *pxping,
1703 ip6_addr_t *src, ip6_addr_t *dst,
1704 int hopl, int tclass, u16_t icmplen)
1705{
1706 struct icmp6_hdr *icmph;
1707 u8_t *bufptr;
1708 size_t buflen, hlen;
1709 int proto;
1710 struct ip6_hdr *oiph;
1711 struct icmp6_echo_hdr *oicmph;
1712 struct ping_pcb *pcb;
1713 ip6_addr_t guest_ip, target_ip, error_ip;
1714 int target_mapped, error_mapped;
1715 u16_t guest_id;
1716 u32_t sum;
1717
1718 icmph = (struct icmp6_hdr *)pollmgr_udpbuf;
1719
1720 /*
1721 * Inner IP datagram is not checked by the kernel and may be
1722 * anything, possibly malicious.
1723 */
1724 oiph = NULL;
1725 oicmph = NULL;
1726
1727 bufptr = pollmgr_udpbuf;
1728 buflen = icmplen;
1729
1730 hlen = sizeof(*icmph);
1731 proto = IP6_NEXTH_ENCAPS; /* i.e. IPv6, lwIP's name is unfortuate */
1732 for (;;) {
1733 if (hlen > buflen) {
1734 DPRINTF2(("truncated datagram inside ICMPv6 error message is too short\n"));
1735 return;
1736 }
1737 buflen -= hlen;
1738 bufptr += hlen;
1739
1740 if (proto == IP6_NEXTH_ENCAPS && oiph == NULL) { /* outermost IPv6 */
1741 oiph = (struct ip6_hdr *)bufptr;
1742 if (IP6H_V(oiph) != 6) {
1743 DPRINTF2(("%s: unexpected IP version %d\n", __func__, IP6H_V(oiph)));
1744 return;
1745 }
1746
1747 proto = IP6H_NEXTH(oiph);
1748 hlen = IP6_HLEN;
1749 }
1750 else if (proto == IP6_NEXTH_ICMP6) {
1751 oicmph = (struct icmp6_echo_hdr *)bufptr;
1752 break;
1753 }
1754 else if (proto == IP6_NEXTH_ROUTING
1755 || proto == IP6_NEXTH_HOPBYHOP
1756 || proto == IP6_NEXTH_DESTOPTS)
1757 {
1758 proto = bufptr[0];
1759 hlen = (bufptr[1] + 1) * 8;
1760 }
1761 else {
1762 DPRINTF2(("%s: stopping at protocol %d\n", __func__, proto));
1763 break;
1764 }
1765 }
1766
1767 if (oiph == NULL || oicmph == NULL) {
1768 return;
1769 }
1770
1771 if (buflen < sizeof(*oicmph)) {
1772 DPRINTF2(("%s: original ICMPv6 is truncated too short\n", __func__));
1773 return;
1774 }
1775
1776 if (oicmph->type != ICMP6_TYPE_EREQ) {
1777 DPRINTF2(("%s: ignoring original ICMPv6 type %d\n", __func__, oicmph->type));
1778 return;
1779 }
1780
1781 memcpy(&target_ip, &oiph->dest, sizeof(target_ip)); /* inner (failed) */
1782 target_mapped = pxremap_inbound_ip6(&target_ip, &target_ip);
1783 if (target_mapped == PXREMAP_FAILED) {
1784 return;
1785 }
1786
1787 sys_mutex_lock(&pxping->lock);
1788 pcb = pxping_pcb_for_reply(pxping, 1, ip_2_ipX(&target_ip), oicmph->id);
1789 if (pcb == NULL) {
1790 sys_mutex_unlock(&pxping->lock);
1791 DPRINTF2(("%s: no match\n", __func__));
1792 return;
1793 }
1794
1795 DPRINTF2(("%s: pcb %p\n", __func__, (void *)pcb));
1796
1797 /* save info before unlocking since pcb may expire */
1798 ip6_addr_copy(guest_ip, *ipX_2_ip6(&pcb->src));
1799 guest_id = pcb->guest_id;
1800
1801 sys_mutex_unlock(&pxping->lock);
1802
1803
1804 /*
1805 * Rewrite inner and outer headers and forward to guest. Note
1806 * that IPv6 has no IP header checksum, but uses pseudo-header for
1807 * ICMPv6, so we update both in one go, adjusting ICMPv6 checksum
1808 * as we rewrite IP header.
1809 */
1810
1811 ip6_addr_copy(error_ip, *src); /* node that reports the error */
1812 error_mapped = pxremap_inbound_ip6(&error_ip, &error_ip);
1813 if (error_mapped == PXREMAP_FAILED) {
1814 return;
1815 }
1816 if (error_mapped == PXREMAP_ASIS && hopl == 1) {
1817 DPRINTF2(("%s: dropping packet with ttl 1\n", __func__));
1818 return;
1819 }
1820
1821 /* rewrite inner ICMPv6 echo header and inner IPv6 header */
1822 sum = (u16_t)~oicmph->chksum;
1823 sum += chksum_update_16(&oicmph->id, guest_id);
1824 sum += chksum_update_ipv6((ip6_addr_t *)&oiph->src, &guest_ip);
1825 if (target_mapped) {
1826 sum += chksum_delta_ipv6((ip6_addr_t *)&oiph->dest, &target_ip);
1827 }
1828 sum = FOLD_U32T(sum);
1829 oicmph->chksum = ~sum;
1830
1831 /* rewrite outer ICMPv6 error header */
1832 sum = (u16_t)~icmph->chksum;
1833 sum += chksum_delta_ipv6(dst, &guest_ip); /* pseudo */
1834 if (error_mapped) {
1835 sum += chksum_delta_ipv6(src, &error_ip); /* pseudo */
1836 }
1837 sum = FOLD_U32T(sum);
1838 icmph->chksum = ~sum;
1839
1840 pxping_pmgr_forward_inbound6(pxping,
1841 &error_ip, /* error src */
1842 &guest_ip, /* error dst */
1843 hopl, tclass, icmplen);
1844}
1845
1846
1847/**
1848 * Hand off ICMP datagram to the lwip thread where it will be
1849 * forwarded to the guest.
1850 *
1851 * We no longer need ping_pcb. The pcb may get expired on the lwip
1852 * thread, but we have already patched necessary information into the
1853 * datagram.
1854 */
1855static void
1856pxping_pmgr_forward_inbound(struct pxping *pxping, u16_t iplen)
1857{
1858 struct pbuf *p;
1859 struct ping_msg *msg;
1860 err_t error;
1861
1862 p = pbuf_alloc(PBUF_LINK, iplen, PBUF_RAM);
1863 if (p == NULL) {
1864 DPRINTF(("%s: pbuf_alloc(%d) failed\n",
1865 __func__, (unsigned int)iplen));
1866 return;
1867 }
1868
1869 error = pbuf_take(p, pollmgr_udpbuf, iplen);
1870 if (error != ERR_OK) {
1871 DPRINTF(("%s: pbuf_take(%d) failed\n",
1872 __func__, (unsigned int)iplen));
1873 pbuf_free(p);
1874 return;
1875 }
1876
1877 msg = (struct ping_msg *)malloc(sizeof(*msg));
1878 if (msg == NULL) {
1879 pbuf_free(p);
1880 return;
1881 }
1882
1883 msg->msg.type = TCPIP_MSG_CALLBACK_STATIC;
1884 msg->msg.sem = NULL;
1885 msg->msg.msg.cb.function = pxping_pcb_forward_inbound;
1886 msg->msg.msg.cb.ctx = (void *)msg;
1887
1888 msg->pxping = pxping;
1889 msg->p = p;
1890
1891 proxy_lwip_post(&msg->msg);
1892}
1893
1894
1895static void
1896pxping_pcb_forward_inbound(void *arg)
1897{
1898 struct ping_msg *msg = (struct ping_msg *)arg;
1899 err_t error;
1900
1901 LWIP_ASSERT1(msg != NULL);
1902 LWIP_ASSERT1(msg->pxping != NULL);
1903 LWIP_ASSERT1(msg->p != NULL);
1904
1905 error = ip_raw_output_if(msg->p, msg->pxping->netif);
1906 if (error != ERR_OK) {
1907 DPRINTF(("%s: ip_output_if: %s\n",
1908 __func__, proxy_lwip_strerr(error)));
1909 }
1910 pbuf_free(msg->p);
1911 free(msg);
1912}
1913
1914
1915static void
1916pxping_pmgr_forward_inbound6(struct pxping *pxping,
1917 ip6_addr_t *src, ip6_addr_t *dst,
1918 u8_t hopl, u8_t tclass,
1919 u16_t icmplen)
1920{
1921 struct pbuf *p;
1922 struct ping6_msg *msg;
1923
1924 err_t error;
1925
1926 p = pbuf_alloc(PBUF_IP, icmplen, PBUF_RAM);
1927 if (p == NULL) {
1928 DPRINTF(("%s: pbuf_alloc(%d) failed\n",
1929 __func__, (unsigned int)icmplen));
1930 return;
1931 }
1932
1933 error = pbuf_take(p, pollmgr_udpbuf, icmplen);
1934 if (error != ERR_OK) {
1935 DPRINTF(("%s: pbuf_take(%d) failed\n",
1936 __func__, (unsigned int)icmplen));
1937 pbuf_free(p);
1938 return;
1939 }
1940
1941 msg = (struct ping6_msg *)malloc(sizeof(*msg));
1942 if (msg == NULL) {
1943 pbuf_free(p);
1944 return;
1945 }
1946
1947 msg->msg.type = TCPIP_MSG_CALLBACK_STATIC;
1948 msg->msg.sem = NULL;
1949 msg->msg.msg.cb.function = pxping_pcb_forward_inbound6;
1950 msg->msg.msg.cb.ctx = (void *)msg;
1951
1952 msg->pxping = pxping;
1953 msg->p = p;
1954 ip6_addr_copy(msg->src, *src);
1955 ip6_addr_copy(msg->dst, *dst);
1956 msg->hopl = hopl;
1957 msg->tclass = tclass;
1958
1959 proxy_lwip_post(&msg->msg);
1960}
1961
1962
1963static void
1964pxping_pcb_forward_inbound6(void *arg)
1965{
1966 struct ping6_msg *msg = (struct ping6_msg *)arg;
1967 err_t error;
1968
1969 LWIP_ASSERT1(msg != NULL);
1970 LWIP_ASSERT1(msg->pxping != NULL);
1971 LWIP_ASSERT1(msg->p != NULL);
1972
1973 error = ip6_output_if(msg->p,
1974 &msg->src, &msg->dst, msg->hopl, msg->tclass,
1975 IP6_NEXTH_ICMP6, msg->pxping->netif);
1976 if (error != ERR_OK) {
1977 DPRINTF(("%s: ip6_output_if: %s\n",
1978 __func__, proxy_lwip_strerr(error)));
1979 }
1980 pbuf_free(msg->p);
1981 free(msg);
1982}
注意: 瀏覽 TracBrowser 來幫助您使用儲存庫瀏覽器

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