VirtualBox

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

Last change on this file since 53381 was 53381, checked in by vboxsync, 10 years ago

NAT: oops, g/c debug log in previous

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Data Id Revision
File size: 12.3 KB
Line 
1/* $Id: ip_icmpwin.c 53381 2014-11-24 02:29:31Z vboxsync $ */
2/** @file
3 * NAT - Windows ICMP API based ping proxy.
4 */
5
6/*
7 * Copyright (C) 2006-2014 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#include "slirp.h"
19#include "ip_icmp.h"
20
21#include <winternl.h> /* for PIO_APC_ROUTINE &c */
22#include <iphlpapi.h>
23#include <icmpapi.h>
24
25/*
26 * A header of ICMP ECHO. Intended for storage, unlike struct icmp
27 * which is intended to be overlayed onto a buffer.
28 */
29struct icmp_echo {
30 uint8_t icmp_type;
31 uint8_t icmp_code;
32 uint16_t icmp_cksum;
33 uint16_t icmp_echo_id;
34 uint16_t icmp_echo_seq;
35};
36
37AssertCompileSize(struct icmp_echo, 8);
38
39
40struct pong {
41 PNATState pData;
42
43 struct ip reqiph;
44 struct icmp_echo reqicmph;
45
46 size_t bufsize;
47 uint8_t buf[1];
48};
49
50
51static VOID WINAPI icmpwin_callback_apc(void *ctx, PIO_STATUS_BLOCK iob, ULONG reserved);
52static VOID WINAPI icmpwin_callback_old(void *ctx);
53
54static void icmpwin_callback(struct pong *pong);
55
56static struct mbuf *icmpwin_get_error(struct pong *pong, int type, int code);
57static struct mbuf *icmpwin_get_mbuf(PNATState pData, size_t reqsize);
58
59
60/*
61 * On Windows XP and Windows Server 2003 IcmpSendEcho2() callback
62 * is FARPROC, but starting from Vista it's PIO_APC_ROUTINE with
63 * two extra arguments. Callbacks use WINAPI (stdcall) calling
64 * convention with callee responsible for popping the arguments,
65 * so to avoid stack corruption we check windows version at run
66 * time and provide correct callback.
67 *
68 * XXX: this is system-wide, but what about multiple NAT threads?
69 */
70static void *pfIcmpCallback;
71
72
73int
74icmpwin_init(PNATState pData)
75{
76 if (pfIcmpCallback == NULL)
77 {
78 OSVERSIONINFO osvi;
79 int status;
80
81 ZeroMemory(&osvi, sizeof(OSVERSIONINFO));
82 osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
83 status = GetVersionEx(&osvi);
84 if (status == 0)
85 return 1;
86
87 if (osvi.dwMajorVersion >= 6)
88 pfIcmpCallback = icmpwin_callback_apc;
89 else
90 pfIcmpCallback = icmpwin_callback_old;
91 }
92
93 pData->icmp_socket.sh = IcmpCreateFile();
94 pData->phEvents[VBOX_ICMP_EVENT_INDEX] = CreateEvent(NULL, FALSE, FALSE, NULL);
95 pData->cbIcmpBuffer = sizeof(ICMP_ECHO_REPLY) * 10;
96 pData->pvIcmpBuffer = RTMemAlloc(pData->cbIcmpBuffer);
97
98 return 0;
99}
100
101
102void
103icmpwin_finit(PNATState pData)
104{
105 IcmpCloseHandle(pData->icmp_socket.sh);
106 RTMemFree(pData->pvIcmpBuffer);
107}
108
109
110/*
111 * Outgoing ping from guest.
112 */
113void
114icmpwin_ping(PNATState pData, struct mbuf *m, int hlen)
115{
116 struct ip *ip = mtod(m, struct ip *);
117 size_t reqsize;
118 uint8_t ttl;
119 size_t bufsize;
120 struct pong *pong;
121 IPAddr dst;
122 IP_OPTION_INFORMATION opts;
123 void *reqdata;
124 int status;
125
126 ttl = ip->ip_ttl;
127 AssertReturnVoid(ttl > 1); /* should've been dealt with in the caller */
128 --ttl;
129
130 reqsize = ip->ip_len - hlen - sizeof(struct icmp_echo);
131
132 bufsize = sizeof(ICMP_ECHO_REPLY);
133 if (reqsize < sizeof(IO_STATUS_BLOCK) + sizeof(struct icmp_echo))
134 bufsize += sizeof(IO_STATUS_BLOCK) + sizeof(struct icmp_echo);
135 else
136 bufsize += reqsize;
137 bufsize += 16; /* whatever that is; empirically at least XP needs it */
138
139 pong = RTMemAlloc(RT_OFFSETOF(struct pong, buf) + bufsize);
140 if (RT_UNLIKELY(pong == NULL))
141 return;
142
143 pong->pData = pData;
144 pong->bufsize = bufsize;
145 m_copydata(m, 0, hlen, (caddr_t)&pong->reqiph);
146 m_copydata(m, hlen, sizeof(struct icmp_echo), (caddr_t)&pong->reqicmph);
147 AssertReturnVoid(pong->reqicmph.icmp_type == ICMP_ECHO);
148
149 if (m->m_next == NULL)
150 {
151 /* already in single contiguous buffer */
152 reqdata = mtod(m, char *) + sizeof(struct ip) + sizeof(struct icmp_echo);
153 }
154 else
155 {
156 /* use reply buffer as temporary storage */
157 reqdata = pong->buf;
158 m_copydata(m, sizeof(struct ip) + sizeof(struct icmp_echo),
159 reqsize, reqdata);
160 }
161
162 dst = ip->ip_dst.s_addr;
163
164 opts.Ttl = ttl;
165 opts.Tos = ip->ip_tos; /* affected by DisableUserTOSSetting key */
166 opts.Flags = (ip->ip_off & IP_DF) != 0 ? IP_FLAG_DF : 0;
167 opts.OptionsSize = 0;
168 opts.OptionsData = 0;
169
170
171 status = IcmpSendEcho2(pData->icmp_socket.sh, NULL,
172 pfIcmpCallback, pong,
173 dst, reqdata, (WORD)reqsize, &opts,
174 pong->buf, (DWORD)pong->bufsize,
175 5 * 1000 /* ms */);
176
177 if (RT_UNLIKELY(status != 0))
178 {
179 Log2(("NAT: IcmpSendEcho2: unexpected status %d\n", status));
180 }
181 else if ((status = GetLastError()) != ERROR_IO_PENDING)
182 {
183 int code;
184
185 Log2(("NAT: IcmpSendEcho2: error %d\n", status));
186 switch (status) {
187 case ERROR_NETWORK_UNREACHABLE:
188 code = ICMP_UNREACH_NET;
189 break;
190 case ERROR_HOST_UNREACHABLE:
191 code = ICMP_UNREACH_HOST;
192 break;
193 default:
194 code = -1;
195 break;
196 }
197
198 if (code != -1) /* send icmp error */
199 {
200 struct mbuf *em = icmpwin_get_error(pong, ICMP_UNREACH, code);
201 if (em != NULL)
202 {
203 struct ip *eip = mtod(em, struct ip *);
204 eip->ip_src = alias_addr;
205 ip_output(pData, NULL, em);
206 }
207 }
208 }
209 else /* success */
210 {
211 Log2(("NAT: pong %p for ping %RTnaipv4 id 0x%04x seq %d len %d\n",
212 pong, dst,
213 RT_N2H_U16(pong->reqicmph.icmp_echo_id),
214 RT_N2H_U16(pong->reqicmph.icmp_echo_seq),
215 reqsize));
216 pong = NULL; /* callback owns it now */
217 }
218
219 if (pong != NULL)
220 RTMemFree(pong);
221}
222
223
224static VOID WINAPI
225icmpwin_callback_apc(void *ctx, PIO_STATUS_BLOCK iob, ULONG reserved)
226{
227 struct pong *pong = (struct pong *)ctx;
228
229 if (pong != NULL)
230 {
231 icmpwin_callback(pong);
232 RTMemFree(pong);
233 }
234}
235
236
237static VOID WINAPI
238icmpwin_callback_old(void *ctx)
239{
240 struct pong *pong = (struct pong *)ctx;
241
242 if (pong != NULL)
243 {
244 icmpwin_callback(pong);
245 RTMemFree(pong);
246 }
247}
248
249
250/*
251 * Actual callback code for IcmpSendEcho2(). OS version specific
252 * trampoline will free "pong" argument for us.
253 */
254static void
255icmpwin_callback(struct pong *pong)
256{
257 PNATState pData;
258 DWORD nreplies;
259 ICMP_ECHO_REPLY *reply;
260 struct mbuf *m;
261 struct ip *ip;
262 struct icmp_echo *icmp;
263 size_t reqsize;
264
265 pData = pong->pData; /* to make slirp_state.h macro hackery work */
266
267 nreplies = IcmpParseReplies(pong->buf, (DWORD)pong->bufsize);
268 if (nreplies == 0)
269 {
270 DWORD error = GetLastError();
271 if (error == IP_REQ_TIMED_OUT)
272 Log2(("NAT: ping %p timed out\n", (void *)pong));
273 else
274 Log2(("NAT: ping %p: IcmpParseReplies: error %d\n",
275 (void *)pong, error));
276 return;
277 }
278
279 reply = (ICMP_ECHO_REPLY *)pong->buf;
280
281 if (reply->Status == IP_SUCCESS)
282 {
283 if (reply->Options.OptionsSize != 0) /* don't do options */
284 return;
285
286 /* need to remap &reply->Address ? */
287 if (/* not a mapped loopback */ 1)
288 {
289 if (reply->Options.Ttl <= 1)
290 return;
291 --reply->Options.Ttl;
292 }
293
294 reqsize = reply->DataSize;
295 if ( (reply->Options.Flags & IP_FLAG_DF) != 0
296 && sizeof(struct ip) + sizeof(struct icmp_echo) + reqsize > if_mtu)
297 return;
298
299 m = icmpwin_get_mbuf(pData, reqsize);
300 if (m == NULL)
301 return;
302
303 ip = mtod(m, struct ip *);
304 icmp = (struct icmp_echo *)(mtod(m, char *) + sizeof(*ip));
305
306 /* fill in ip (ip_output0() does the boilerplate for us) */
307 ip->ip_tos = reply->Options.Tos;
308 ip->ip_len = sizeof(*ip) + sizeof(*icmp) + reqsize;
309 ip->ip_off = 0;
310 ip->ip_ttl = reply->Options.Ttl;
311 ip->ip_p = IPPROTO_ICMP;
312 ip->ip_src.s_addr = reply->Address;
313 ip->ip_dst = pong->reqiph.ip_src;
314
315 icmp->icmp_type = ICMP_ECHOREPLY;
316 icmp->icmp_code = 0;
317 icmp->icmp_cksum = 0;
318 icmp->icmp_echo_id = pong->reqicmph.icmp_echo_id;
319 icmp->icmp_echo_seq = pong->reqicmph.icmp_echo_seq;
320
321 m_append(pData, m, reqsize, reply->Data);
322
323 icmp->icmp_cksum = in_cksum_skip(m, ip->ip_len, sizeof(*ip));
324 }
325 else {
326 uint8_t type, code;
327
328 switch (reply->Status) {
329 case IP_DEST_NET_UNREACHABLE:
330 type = ICMP_UNREACH; code = ICMP_UNREACH_NET;
331 break;
332 case IP_DEST_HOST_UNREACHABLE:
333 type = ICMP_UNREACH; code = ICMP_UNREACH_HOST;
334 break;
335 case IP_DEST_PROT_UNREACHABLE:
336 type = ICMP_UNREACH; code = ICMP_UNREACH_PROTOCOL;
337 break;
338 case IP_PACKET_TOO_BIG:
339 type = ICMP_UNREACH; code = ICMP_UNREACH_NEEDFRAG;
340 break;
341 case IP_SOURCE_QUENCH:
342 type = ICMP_SOURCEQUENCH; code = 0;
343 break;
344 case IP_TTL_EXPIRED_TRANSIT:
345 type = ICMP_TIMXCEED; code = ICMP_TIMXCEED_INTRANS;
346 break;
347 case IP_TTL_EXPIRED_REASSEM:
348 type = ICMP_TIMXCEED; code = ICMP_TIMXCEED_REASS;
349 break;
350 default:
351 Log2(("NAT: ping reply status %d, dropped\n", reply->Status));
352 return;
353 }
354
355 Log2(("NAT: ping status %d -> type %d/code %d\n",
356 reply->Status, type, code));
357
358 /*
359 * XXX: we don't know the TTL of the request at the time this
360 * ICMP error was generated (we can guess it was 1 for ttl
361 * exceeded, but don't bother faking it).
362 */
363 m = icmpwin_get_error(pong, type, code);
364 if (m == NULL)
365 return;
366
367 ip = mtod(m, struct ip *);
368
369 ip->ip_tos = reply->Options.Tos;
370 ip->ip_ttl = reply->Options.Ttl; /* XXX: decrement */
371 ip->ip_src.s_addr = reply->Address;
372 }
373
374 Assert(ip->ip_len == m_length(m, NULL));
375 ip_output(pData, NULL, m);
376}
377
378
379/*
380 * Prepare mbuf with ICMP error type/code.
381 * IP source must be filled by the caller.
382 */
383static struct mbuf *
384icmpwin_get_error(struct pong *pong, int type, int code)
385{
386 PNATState pData = pong->pData;
387 struct mbuf *m;
388 struct ip *ip;
389 struct icmp_echo *icmp;
390 size_t reqsize;
391
392 Log2(("NAT: ping error type %d/code %d\n", type, code));
393
394 reqsize = sizeof(pong->reqiph) + sizeof(pong->reqicmph);
395
396 m = icmpwin_get_mbuf(pData, reqsize);
397 if (m == NULL)
398 return NULL;
399
400 ip = mtod(m, struct ip *);
401 icmp = (struct icmp_echo *)(mtod(m, char *) + sizeof(*ip));
402
403 ip->ip_tos = 0;
404 ip->ip_len = sizeof(*ip) + sizeof(*icmp) + reqsize;
405 ip->ip_off = 0;
406 ip->ip_ttl = IPDEFTTL;
407 ip->ip_p = IPPROTO_ICMP;
408 ip->ip_src.s_addr = 0; /* NB */
409 ip->ip_dst = pong->reqiph.ip_src;
410
411 icmp->icmp_type = type;
412 icmp->icmp_code = code;
413 icmp->icmp_cksum = 0;
414 icmp->icmp_echo_id = 0;
415 icmp->icmp_echo_seq = 0;
416
417 m_append(pData, m, sizeof(pong->reqiph), (caddr_t)&pong->reqiph);
418 m_append(pData, m, sizeof(pong->reqicmph), (caddr_t)&pong->reqicmph);
419
420 icmp->icmp_cksum = in_cksum_skip(m, ip->ip_len, sizeof(*ip));
421
422 return m;
423}
424
425
426/*
427 * Replacing original simple slirp mbufs with real mbufs from freebsd
428 * was a bit messy since assumption are different. This leads to
429 * rather ugly code at times. Hide the gore here.
430 */
431static struct mbuf *
432icmpwin_get_mbuf(PNATState pData, size_t reqsize)
433{
434 struct mbuf *m;
435
436 reqsize += if_maxlinkhdr;
437 reqsize += sizeof(struct ip) + sizeof(struct icmp_echo);
438
439 if (reqsize <= MHLEN)
440 /* good pings come in small packets */
441 m = m_gethdr(pData, M_NOWAIT, MT_HEADER);
442 else
443 m = m_getjcl(pData, M_NOWAIT, MT_HEADER, M_PKTHDR, slirp_size(pData));
444
445 if (m == NULL)
446 return NULL;
447
448 m->m_flags |= M_SKIP_FIREWALL;
449 m->m_data += if_maxlinkhdr; /* reserve leading space for ethernet header */
450
451 m->m_pkthdr.header = mtod(m, void *);
452 m->m_len = sizeof(struct ip) + sizeof(struct icmp_echo);
453
454 return m;
455}
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