VirtualBox

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

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

NAT: fix in_cksum_skip() calls - "len" parameter must include the
"skip"ped amount.

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