VirtualBox

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

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

NAT: allocate extra space for ICMP API internal use if reqest size is too small

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Data Id Revision
File size: 12.4 KB
Line 
1/* $Id: ip_icmpwin.c 53380 2014-11-24 02:28:53Z 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 LogRel(("NAT: ping size=%d bufsize=%d\n", (int)reqsize, (int)bufsize));
139
140 pong = RTMemAlloc(RT_OFFSETOF(struct pong, buf) + bufsize);
141 if (RT_UNLIKELY(pong == NULL))
142 return;
143
144 pong->pData = pData;
145 pong->bufsize = bufsize;
146 m_copydata(m, 0, hlen, (caddr_t)&pong->reqiph);
147 m_copydata(m, hlen, sizeof(struct icmp_echo), (caddr_t)&pong->reqicmph);
148 AssertReturnVoid(pong->reqicmph.icmp_type == ICMP_ECHO);
149
150 if (m->m_next == NULL)
151 {
152 /* already in single contiguous buffer */
153 reqdata = mtod(m, char *) + sizeof(struct ip) + sizeof(struct icmp_echo);
154 }
155 else
156 {
157 /* use reply buffer as temporary storage */
158 reqdata = pong->buf;
159 m_copydata(m, sizeof(struct ip) + sizeof(struct icmp_echo),
160 reqsize, reqdata);
161 }
162
163 dst = ip->ip_dst.s_addr;
164
165 opts.Ttl = ttl;
166 opts.Tos = ip->ip_tos; /* affected by DisableUserTOSSetting key */
167 opts.Flags = (ip->ip_off & IP_DF) != 0 ? IP_FLAG_DF : 0;
168 opts.OptionsSize = 0;
169 opts.OptionsData = 0;
170
171
172 status = IcmpSendEcho2(pData->icmp_socket.sh, NULL,
173 pfIcmpCallback, pong,
174 dst, reqdata, (WORD)reqsize, &opts,
175 pong->buf, (DWORD)pong->bufsize,
176 5 * 1000 /* ms */);
177
178 if (RT_UNLIKELY(status != 0))
179 {
180 Log2(("NAT: IcmpSendEcho2: unexpected status %d\n", status));
181 }
182 else if ((status = GetLastError()) != ERROR_IO_PENDING)
183 {
184 int code;
185
186 Log2(("NAT: IcmpSendEcho2: error %d\n", status));
187 switch (status) {
188 case ERROR_NETWORK_UNREACHABLE:
189 code = ICMP_UNREACH_NET;
190 break;
191 case ERROR_HOST_UNREACHABLE:
192 code = ICMP_UNREACH_HOST;
193 break;
194 default:
195 code = -1;
196 break;
197 }
198
199 if (code != -1) /* send icmp error */
200 {
201 struct mbuf *em = icmpwin_get_error(pong, ICMP_UNREACH, code);
202 if (em != NULL)
203 {
204 struct ip *eip = mtod(em, struct ip *);
205 eip->ip_src = alias_addr;
206 ip_output(pData, NULL, em);
207 }
208 }
209 }
210 else /* success */
211 {
212 Log2(("NAT: pong %p for ping %RTnaipv4 id 0x%04x seq %d len %d\n",
213 pong, dst,
214 RT_N2H_U16(pong->reqicmph.icmp_echo_id),
215 RT_N2H_U16(pong->reqicmph.icmp_echo_seq),
216 reqsize));
217 pong = NULL; /* callback owns it now */
218 }
219
220 if (pong != NULL)
221 RTMemFree(pong);
222}
223
224
225static VOID WINAPI
226icmpwin_callback_apc(void *ctx, PIO_STATUS_BLOCK iob, ULONG reserved)
227{
228 struct pong *pong = (struct pong *)ctx;
229
230 if (pong != NULL)
231 {
232 icmpwin_callback(pong);
233 RTMemFree(pong);
234 }
235}
236
237
238static VOID WINAPI
239icmpwin_callback_old(void *ctx)
240{
241 struct pong *pong = (struct pong *)ctx;
242
243 if (pong != NULL)
244 {
245 icmpwin_callback(pong);
246 RTMemFree(pong);
247 }
248}
249
250
251/*
252 * Actual callback code for IcmpSendEcho2(). OS version specific
253 * trampoline will free "pong" argument for us.
254 */
255static void
256icmpwin_callback(struct pong *pong)
257{
258 PNATState pData;
259 DWORD nreplies;
260 ICMP_ECHO_REPLY *reply;
261 struct mbuf *m;
262 struct ip *ip;
263 struct icmp_echo *icmp;
264 size_t reqsize;
265
266 pData = pong->pData; /* to make slirp_state.h macro hackery work */
267
268 nreplies = IcmpParseReplies(pong->buf, (DWORD)pong->bufsize);
269 if (nreplies == 0)
270 {
271 DWORD error = GetLastError();
272 if (error == IP_REQ_TIMED_OUT)
273 Log2(("NAT: ping %p timed out\n", (void *)pong));
274 else
275 Log2(("NAT: ping %p: IcmpParseReplies: error %d\n",
276 (void *)pong, error));
277 return;
278 }
279
280 reply = (ICMP_ECHO_REPLY *)pong->buf;
281
282 if (reply->Status == IP_SUCCESS)
283 {
284 if (reply->Options.OptionsSize != 0) /* don't do options */
285 return;
286
287 /* need to remap &reply->Address ? */
288 if (/* not a mapped loopback */ 1)
289 {
290 if (reply->Options.Ttl <= 1)
291 return;
292 --reply->Options.Ttl;
293 }
294
295 reqsize = reply->DataSize;
296 if ( (reply->Options.Flags & IP_FLAG_DF) != 0
297 && sizeof(struct ip) + sizeof(struct icmp_echo) + reqsize > if_mtu)
298 return;
299
300 m = icmpwin_get_mbuf(pData, reqsize);
301 if (m == NULL)
302 return;
303
304 ip = mtod(m, struct ip *);
305 icmp = (struct icmp_echo *)(mtod(m, char *) + sizeof(*ip));
306
307 /* fill in ip (ip_output0() does the boilerplate for us) */
308 ip->ip_tos = reply->Options.Tos;
309 ip->ip_len = sizeof(*ip) + sizeof(*icmp) + reqsize;
310 ip->ip_off = 0;
311 ip->ip_ttl = reply->Options.Ttl;
312 ip->ip_p = IPPROTO_ICMP;
313 ip->ip_src.s_addr = reply->Address;
314 ip->ip_dst = pong->reqiph.ip_src;
315
316 icmp->icmp_type = ICMP_ECHOREPLY;
317 icmp->icmp_code = 0;
318 icmp->icmp_cksum = 0;
319 icmp->icmp_echo_id = pong->reqicmph.icmp_echo_id;
320 icmp->icmp_echo_seq = pong->reqicmph.icmp_echo_seq;
321
322 m_append(pData, m, reqsize, reply->Data);
323
324 icmp->icmp_cksum = in_cksum_skip(m, ip->ip_len, sizeof(*ip));
325 }
326 else {
327 uint8_t type, code;
328
329 switch (reply->Status) {
330 case IP_DEST_NET_UNREACHABLE:
331 type = ICMP_UNREACH; code = ICMP_UNREACH_NET;
332 break;
333 case IP_DEST_HOST_UNREACHABLE:
334 type = ICMP_UNREACH; code = ICMP_UNREACH_HOST;
335 break;
336 case IP_DEST_PROT_UNREACHABLE:
337 type = ICMP_UNREACH; code = ICMP_UNREACH_PROTOCOL;
338 break;
339 case IP_PACKET_TOO_BIG:
340 type = ICMP_UNREACH; code = ICMP_UNREACH_NEEDFRAG;
341 break;
342 case IP_SOURCE_QUENCH:
343 type = ICMP_SOURCEQUENCH; code = 0;
344 break;
345 case IP_TTL_EXPIRED_TRANSIT:
346 type = ICMP_TIMXCEED; code = ICMP_TIMXCEED_INTRANS;
347 break;
348 case IP_TTL_EXPIRED_REASSEM:
349 type = ICMP_TIMXCEED; code = ICMP_TIMXCEED_REASS;
350 break;
351 default:
352 Log2(("NAT: ping reply status %d, dropped\n", reply->Status));
353 return;
354 }
355
356 Log2(("NAT: ping status %d -> type %d/code %d\n",
357 reply->Status, type, code));
358
359 /*
360 * XXX: we don't know the TTL of the request at the time this
361 * ICMP error was generated (we can guess it was 1 for ttl
362 * exceeded, but don't bother faking it).
363 */
364 m = icmpwin_get_error(pong, type, code);
365 if (m == NULL)
366 return;
367
368 ip = mtod(m, struct ip *);
369
370 ip->ip_tos = reply->Options.Tos;
371 ip->ip_ttl = reply->Options.Ttl; /* XXX: decrement */
372 ip->ip_src.s_addr = reply->Address;
373 }
374
375 Assert(ip->ip_len == m_length(m, NULL));
376 ip_output(pData, NULL, m);
377}
378
379
380/*
381 * Prepare mbuf with ICMP error type/code.
382 * IP source must be filled by the caller.
383 */
384static struct mbuf *
385icmpwin_get_error(struct pong *pong, int type, int code)
386{
387 PNATState pData = pong->pData;
388 struct mbuf *m;
389 struct ip *ip;
390 struct icmp_echo *icmp;
391 size_t reqsize;
392
393 Log2(("NAT: ping error type %d/code %d\n", type, code));
394
395 reqsize = sizeof(pong->reqiph) + sizeof(pong->reqicmph);
396
397 m = icmpwin_get_mbuf(pData, reqsize);
398 if (m == NULL)
399 return NULL;
400
401 ip = mtod(m, struct ip *);
402 icmp = (struct icmp_echo *)(mtod(m, char *) + sizeof(*ip));
403
404 ip->ip_tos = 0;
405 ip->ip_len = sizeof(*ip) + sizeof(*icmp) + reqsize;
406 ip->ip_off = 0;
407 ip->ip_ttl = IPDEFTTL;
408 ip->ip_p = IPPROTO_ICMP;
409 ip->ip_src.s_addr = 0; /* NB */
410 ip->ip_dst = pong->reqiph.ip_src;
411
412 icmp->icmp_type = type;
413 icmp->icmp_code = code;
414 icmp->icmp_cksum = 0;
415 icmp->icmp_echo_id = 0;
416 icmp->icmp_echo_seq = 0;
417
418 m_append(pData, m, sizeof(pong->reqiph), (caddr_t)&pong->reqiph);
419 m_append(pData, m, sizeof(pong->reqicmph), (caddr_t)&pong->reqicmph);
420
421 icmp->icmp_cksum = in_cksum_skip(m, ip->ip_len, sizeof(*ip));
422
423 return m;
424}
425
426
427/*
428 * Replacing original simple slirp mbufs with real mbufs from freebsd
429 * was a bit messy since assumption are different. This leads to
430 * rather ugly code at times. Hide the gore here.
431 */
432static struct mbuf *
433icmpwin_get_mbuf(PNATState pData, size_t reqsize)
434{
435 struct mbuf *m;
436
437 reqsize += if_maxlinkhdr;
438 reqsize += sizeof(struct ip) + sizeof(struct icmp_echo);
439
440 if (reqsize <= MHLEN)
441 /* good pings come in small packets */
442 m = m_gethdr(pData, M_NOWAIT, MT_HEADER);
443 else
444 m = m_getjcl(pData, M_NOWAIT, MT_HEADER, M_PKTHDR, slirp_size(pData));
445
446 if (m == NULL)
447 return NULL;
448
449 m->m_flags |= M_SKIP_FIREWALL;
450 m->m_data += if_maxlinkhdr; /* reserve leading space for ethernet header */
451
452 m->m_pkthdr.header = mtod(m, void *);
453 m->m_len = sizeof(struct ip) + sizeof(struct icmp_echo);
454
455 return m;
456}
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