Print this page
3168 pfmod commands could be more useful
Split |
Close |
Expand all |
Collapse all |
--- old/usr/src/uts/common/io/pfmod.c
+++ new/usr/src/uts/common/io/pfmod.c
1 1 /*
2 2 * CDDL HEADER START
3 3 *
4 4 * The contents of this file are subject to the terms of the
5 5 * Common Development and Distribution License (the "License").
6 6 * You may not use this file except in compliance with the License.
7 7 *
8 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 9 * or http://www.opensolaris.org/os/licensing.
10 10 * See the License for the specific language governing permissions
11 11 * and limitations under the License.
12 12 *
13 13 * When distributing Covered Code, include this CDDL HEADER in each
14 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 15 * If applicable, add the following below this CDDL HEADER, with the
↓ open down ↓ |
15 lines elided |
↑ open up ↑ |
16 16 * fields enclosed by brackets "[]" replaced with your own identifying
17 17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 18 *
19 19 * CDDL HEADER END
20 20 */
21 21 /*
22 22 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
23 23 * Use is subject to license terms.
24 24 */
25 25
26 -#pragma ident "%Z%%M% %I% %E% SMI"
27 -
28 26 /*
29 27 * STREAMS Packet Filter Module
30 28 *
31 29 * This module applies a filter to messages arriving on its read
32 30 * queue, passing on messages that the filter accepts adn discarding
33 31 * the others. It supports ioctls for setting the filter.
34 32 *
35 33 * On the write side, the module simply passes everything through
36 34 * unchanged.
37 35 *
38 36 * Based on SunOS 4.x version. This version has minor changes:
39 37 * - general SVR4 porting stuff
40 38 * - change name and prefixes from "nit" buffer to streams buffer
41 39 * - multithreading assumes configured as D_MTQPAIR
42 40 */
43 41
44 42 #include <sys/types.h>
45 43 #include <sys/sysmacros.h>
46 44 #include <sys/errno.h>
47 45 #include <sys/debug.h>
↓ open down ↓ |
10 lines elided |
↑ open up ↑ |
48 46 #include <sys/time.h>
49 47 #include <sys/stropts.h>
50 48 #include <sys/stream.h>
51 49 #include <sys/conf.h>
52 50 #include <sys/ddi.h>
53 51 #include <sys/sunddi.h>
54 52 #include <sys/kmem.h>
55 53 #include <sys/strsun.h>
56 54 #include <sys/pfmod.h>
57 55 #include <sys/modctl.h>
56 +#include <netinet/in.h>
58 57
59 58 /*
60 59 * Expanded version of the Packetfilt structure that includes
61 60 * some additional fields that aid filter execution efficiency.
62 61 */
63 62 struct epacketfilt {
64 63 struct Pf_ext_packetfilt pf;
65 64 #define pf_Priority pf.Pf_Priority
66 65 #define pf_FilterLen pf.Pf_FilterLen
67 66 #define pf_Filter pf.Pf_Filter
68 67 /* pointer to word immediately past end of filter */
69 68 ushort_t *pf_FilterEnd;
70 69 /* length in bytes of packet prefix the filter examines */
71 70 ushort_t pf_PByteLen;
72 71 };
73 72
74 73 /*
75 74 * (Internal) packet descriptor for FilterPacket
76 75 */
77 76 struct packdesc {
78 77 ushort_t *pd_hdr; /* header starting address */
79 78 uint_t pd_hdrlen; /* header length in shorts */
80 79 ushort_t *pd_body; /* body starting address */
81 80 uint_t pd_bodylen; /* body length in shorts */
82 81 };
83 82
84 83
85 84 /*
86 85 * Function prototypes.
87 86 */
88 87 static int pfopen(queue_t *, dev_t *, int, int, cred_t *);
89 88 static int pfclose(queue_t *);
90 89 static void pfioctl(queue_t *wq, mblk_t *mp);
91 90 static int FilterPacket(struct packdesc *, struct epacketfilt *);
92 91 /*
93 92 * To save instructions, since STREAMS ignores the return value
94 93 * from these functions, they are defined as void here. Kind of icky, but...
95 94 */
96 95 static void pfwput(queue_t *, mblk_t *);
97 96 static void pfrput(queue_t *, mblk_t *);
98 97
99 98 static struct module_info pf_minfo = {
100 99 22, /* mi_idnum */
101 100 "pfmod", /* mi_idname */
102 101 0, /* mi_minpsz */
103 102 INFPSZ, /* mi_maxpsz */
104 103 0, /* mi_hiwat */
105 104 0 /* mi_lowat */
106 105 };
107 106
108 107 static struct qinit pf_rinit = {
109 108 (int (*)())pfrput, /* qi_putp */
110 109 NULL,
111 110 pfopen, /* qi_qopen */
112 111 pfclose, /* qi_qclose */
113 112 NULL, /* qi_qadmin */
114 113 &pf_minfo, /* qi_minfo */
115 114 NULL /* qi_mstat */
116 115 };
117 116
118 117 static struct qinit pf_winit = {
119 118 (int (*)())pfwput, /* qi_putp */
120 119 NULL, /* qi_srvp */
121 120 NULL, /* qi_qopen */
122 121 NULL, /* qi_qclose */
123 122 NULL, /* qi_qadmin */
124 123 &pf_minfo, /* qi_minfo */
125 124 NULL /* qi_mstat */
126 125 };
127 126
128 127 static struct streamtab pf_info = {
129 128 &pf_rinit, /* st_rdinit */
130 129 &pf_winit, /* st_wrinit */
131 130 NULL, /* st_muxrinit */
132 131 NULL /* st_muxwinit */
133 132 };
134 133
135 134 static struct fmodsw fsw = {
136 135 "pfmod",
137 136 &pf_info,
138 137 D_MTQPAIR | D_MP
139 138 };
140 139
141 140 static struct modlstrmod modlstrmod = {
142 141 &mod_strmodops, "streams packet filter module", &fsw
143 142 };
144 143
145 144 static struct modlinkage modlinkage = {
146 145 MODREV_1, &modlstrmod, NULL
147 146 };
148 147
149 148 int
150 149 _init(void)
151 150 {
152 151 return (mod_install(&modlinkage));
153 152 }
154 153
155 154 int
156 155 _fini(void)
157 156 {
158 157 return (mod_remove(&modlinkage));
159 158 }
160 159
161 160 int
162 161 _info(struct modinfo *modinfop)
163 162 {
164 163 return (mod_info(&modlinkage, modinfop));
165 164 }
166 165
167 166 /*ARGSUSED*/
168 167 static int
169 168 pfopen(queue_t *rq, dev_t *dev, int oflag, int sflag, cred_t *crp)
170 169 {
171 170 struct epacketfilt *pfp;
172 171
173 172 ASSERT(rq);
174 173
175 174 if (sflag != MODOPEN)
176 175 return (EINVAL);
177 176
178 177 if (rq->q_ptr)
179 178 return (0);
180 179
181 180 /*
182 181 * Allocate and initialize per-Stream structure.
183 182 */
184 183 pfp = kmem_alloc(sizeof (struct epacketfilt), KM_SLEEP);
185 184 rq->q_ptr = WR(rq)->q_ptr = (char *)pfp;
186 185
187 186 qprocson(rq);
188 187
189 188 return (0);
190 189 }
191 190
192 191 static int
193 192 pfclose(queue_t *rq)
194 193 {
195 194 struct epacketfilt *pfp = (struct epacketfilt *)rq->q_ptr;
196 195
197 196 ASSERT(pfp);
198 197
199 198 qprocsoff(rq);
200 199
201 200 kmem_free(pfp, sizeof (struct epacketfilt));
202 201 rq->q_ptr = WR(rq)->q_ptr = NULL;
203 202
204 203 return (0);
205 204 }
206 205
207 206 /*
208 207 * Write-side put procedure. Its main task is to detect ioctls.
209 208 * Other message types are passed on through.
210 209 */
211 210 static void
212 211 pfwput(queue_t *wq, mblk_t *mp)
213 212 {
214 213 switch (mp->b_datap->db_type) {
215 214 case M_IOCTL:
216 215 pfioctl(wq, mp);
217 216 break;
218 217
219 218 default:
220 219 putnext(wq, mp);
221 220 break;
222 221 }
223 222 }
224 223
225 224 /*
226 225 * Read-side put procedure. It's responsible for applying the
227 226 * packet filter and passing upstream message on or discarding it
228 227 * depending upon the results.
229 228 *
230 229 * Upstream messages can start with zero or more M_PROTO mblks
231 230 * which are skipped over before executing the packet filter
232 231 * on any remaining M_DATA mblks.
233 232 */
234 233 static void
235 234 pfrput(queue_t *rq, mblk_t *mp)
236 235 {
237 236 struct epacketfilt *pfp = (struct epacketfilt *)rq->q_ptr;
238 237 mblk_t *mbp, *mpp;
239 238 struct packdesc pd;
240 239 int need;
241 240
242 241 ASSERT(pfp);
243 242
244 243 switch (DB_TYPE(mp)) {
245 244 case M_PROTO:
246 245 case M_DATA:
247 246 /*
248 247 * Skip over protocol information and find the start
249 248 * of the message body, saving the overall message
250 249 * start in mpp.
251 250 */
252 251 for (mpp = mp; mp && (DB_TYPE(mp) == M_PROTO); mp = mp->b_cont)
253 252 ;
254 253
255 254 /*
256 255 * Null body (exclusive of M_PROTO blocks) ==> accept.
257 256 * Note that a null body is not the same as an empty body.
258 257 */
259 258 if (mp == NULL) {
260 259 putnext(rq, mpp);
261 260 break;
262 261 }
263 262
264 263 /*
265 264 * Pull the packet up to the length required by
266 265 * the filter. Note that doing so destroys sharing
267 266 * relationships, which is unfortunate, since the
268 267 * results of pulling up here are likely to be useful
269 268 * for shared messages applied to a filter on a sibling
270 269 * stream.
271 270 *
272 271 * Most packet sources will provide the packet in two
273 272 * logical pieces: an initial header in a single mblk,
274 273 * and a body in a sequence of mblks hooked to the
275 274 * header. We're prepared to deal with variant forms,
276 275 * but in any case, the pullup applies only to the body
277 276 * part.
278 277 */
279 278 mbp = mp->b_cont;
280 279 need = pfp->pf_PByteLen;
281 280 if (mbp && (MBLKL(mbp) < need)) {
282 281 int len = msgdsize(mbp);
283 282
284 283 /* XXX discard silently on pullupmsg failure */
285 284 if (pullupmsg(mbp, MIN(need, len)) == 0) {
286 285 freemsg(mpp);
287 286 break;
288 287 }
289 288 }
290 289
291 290 /*
292 291 * Misalignment (not on short boundary) ==> reject.
293 292 */
294 293 if (((uintptr_t)mp->b_rptr & (sizeof (ushort_t) - 1)) ||
295 294 (mbp != NULL &&
296 295 ((uintptr_t)mbp->b_rptr & (sizeof (ushort_t) - 1)))) {
297 296 freemsg(mpp);
298 297 break;
299 298 }
300 299
301 300 /*
↓ open down ↓ |
234 lines elided |
↑ open up ↑ |
302 301 * These assignments are distasteful, but necessary,
303 302 * since the packet filter wants to work in terms of
304 303 * shorts. Odd bytes at the end of header or data can't
305 304 * participate in the filtering operation.
306 305 */
307 306 pd.pd_hdr = (ushort_t *)mp->b_rptr;
308 307 pd.pd_hdrlen = (mp->b_wptr - mp->b_rptr) / sizeof (ushort_t);
309 308 if (mbp) {
310 309 pd.pd_body = (ushort_t *)mbp->b_rptr;
311 310 pd.pd_bodylen = (mbp->b_wptr - mbp->b_rptr) /
312 - sizeof (ushort_t);
311 + sizeof (ushort_t);
313 312 } else {
314 313 pd.pd_body = NULL;
315 314 pd.pd_bodylen = 0;
316 315 }
317 316
318 317 /*
319 318 * Apply the filter.
320 319 */
321 320 if (FilterPacket(&pd, pfp))
322 321 putnext(rq, mpp);
323 322 else
324 323 freemsg(mpp);
325 324
326 325 break;
327 326
328 327 default:
329 328 putnext(rq, mp);
330 329 break;
331 330 }
332 331
333 332 }
334 333
335 334 /*
336 335 * Handle write-side M_IOCTL messages.
337 336 */
338 337 static void
339 338 pfioctl(queue_t *wq, mblk_t *mp)
340 339 {
341 340 struct epacketfilt *pfp = (struct epacketfilt *)wq->q_ptr;
342 341 struct Pf_ext_packetfilt *upfp;
343 342 struct packetfilt *opfp;
344 343 ushort_t *fwp;
345 344 int arg;
346 345 int maxoff = 0;
347 346 int maxoffreg = 0;
348 347 struct iocblk *iocp = (struct iocblk *)mp->b_rptr;
349 348 int error;
350 349
351 350 switch (iocp->ioc_cmd) {
352 351 case PFIOCSETF:
353 352 /*
354 353 * Verify argument length. Since the size of packet filter
355 354 * got increased (ENMAXFILTERS was bumped up to 2047), to
356 355 * maintain backwards binary compatibility, we need to
357 356 * check for both possible sizes.
358 357 */
359 358 switch (iocp->ioc_count) {
360 359 case sizeof (struct Pf_ext_packetfilt):
361 360 error = miocpullup(mp,
362 361 sizeof (struct Pf_ext_packetfilt));
363 362 if (error != 0) {
364 363 miocnak(wq, mp, 0, error);
365 364 return;
366 365 }
367 366 upfp = (struct Pf_ext_packetfilt *)mp->b_cont->b_rptr;
368 367 if (upfp->Pf_FilterLen > PF_MAXFILTERS) {
369 368 miocnak(wq, mp, 0, EINVAL);
370 369 return;
371 370 }
372 371
373 372 bcopy(upfp, pfp, sizeof (struct Pf_ext_packetfilt));
374 373 pfp->pf_FilterEnd = &pfp->pf_Filter[pfp->pf_FilterLen];
375 374 break;
376 375
377 376 case sizeof (struct packetfilt):
378 377 error = miocpullup(mp, sizeof (struct packetfilt));
379 378 if (error != 0) {
380 379 miocnak(wq, mp, 0, error);
381 380 return;
382 381 }
383 382 opfp = (struct packetfilt *)mp->b_cont->b_rptr;
384 383 /* this strange comparison keeps gcc from complaining */
385 384 if (opfp->Pf_FilterLen - 1 >= ENMAXFILTERS) {
386 385 miocnak(wq, mp, 0, EINVAL);
387 386 return;
388 387 }
389 388
390 389 pfp->pf.Pf_Priority = opfp->Pf_Priority;
391 390 pfp->pf.Pf_FilterLen = (unsigned int)opfp->Pf_FilterLen;
392 391
393 392 bcopy(opfp->Pf_Filter, pfp->pf.Pf_Filter,
394 393 sizeof (opfp->Pf_Filter));
395 394 pfp->pf_FilterEnd = &pfp->pf_Filter[pfp->pf_FilterLen];
396 395 break;
397 396
398 397 default:
399 398 miocnak(wq, mp, 0, EINVAL);
400 399 return;
401 400 }
402 401
403 402 /*
404 403 * Find and record maximum byte offset that the
405 404 * filter users. We use this when executing the
406 405 * filter to determine how much of the packet
407 406 * body to pull up. This code depends on the
408 407 * filter encoding.
409 408 */
410 409 for (fwp = pfp->pf_Filter; fwp < pfp->pf_FilterEnd; fwp++) {
411 410 arg = *fwp & ((1 << ENF_NBPA) - 1);
412 411 switch (arg) {
413 412 default:
414 413 if ((arg -= ENF_PUSHWORD) > maxoff)
415 414 maxoff = arg;
416 415 break;
417 416
418 417 case ENF_LOAD_OFFSET:
419 418 /* Point to the offset */
420 419 fwp++;
421 420 if (*fwp > maxoffreg)
422 421 maxoffreg = *fwp;
423 422 break;
424 423
425 424 case ENF_PUSHLIT:
426 425 case ENF_BRTR:
↓ open down ↓ |
104 lines elided |
↑ open up ↑ |
427 426 case ENF_BRFL:
428 427 /* Skip over the literal. */
429 428 fwp++;
430 429 break;
431 430
432 431 case ENF_PUSHZERO:
433 432 case ENF_PUSHONE:
434 433 case ENF_PUSHFFFF:
435 434 case ENF_PUSHFF00:
436 435 case ENF_PUSH00FF:
436 + case ENF_PUSHFF00_N:
437 + case ENF_PUSH00FF_N:
437 438 case ENF_NOPUSH:
438 439 case ENF_POP:
439 440 break;
440 441 }
441 442 }
442 443
443 444 /*
444 445 * Convert word offset to length in bytes.
445 446 */
446 447 pfp->pf_PByteLen = (maxoff + maxoffreg + 1) * sizeof (ushort_t);
447 448 miocack(wq, mp, 0, 0);
448 449 break;
449 450
450 451 default:
451 452 putnext(wq, mp);
452 453 break;
453 454 }
454 455 }
455 456
456 457 /* #define DEBUG 1 */
457 458 /* #define INNERDEBUG 1 */
458 459
459 460 #ifdef INNERDEBUG
460 461 #define enprintf(a) printf a
461 462 #else
462 463 #define enprintf(a)
463 464 #endif
464 465
465 466 /*
466 467 * Apply the packet filter given by pfp to the packet given by
467 468 * pp. Return nonzero iff the filter accepts the packet.
468 469 *
469 470 * The packet comes in two pieces, a header and a body, since
470 471 * that's the most convenient form for our caller. The header
471 472 * is in contiguous memory, whereas the body is in a mbuf.
472 473 * Our caller will have adjusted the mbuf chain so that its first
473 474 * min(MLEN, length(body)) bytes are guaranteed contiguous. For
474 475 * the sake of efficiency (and some laziness) the filter is prepared
475 476 * to examine only these two contiguous pieces. Furthermore, it
476 477 * assumes that the header length is even, so that there's no need
477 478 * to glue the last byte of header to the first byte of data.
478 479 */
479 480
480 481 #define opx(i) ((i) >> ENF_NBPA)
481 482
482 483 static int
483 484 FilterPacket(struct packdesc *pp, struct epacketfilt *pfp)
484 485 {
485 486 int maxhdr = pp->pd_hdrlen;
486 487 int maxword = maxhdr + pp->pd_bodylen;
487 488 ushort_t *sp;
488 489 ushort_t *fp;
489 490 ushort_t *fpe;
490 491 unsigned op;
491 492 unsigned arg;
492 493 unsigned offreg = 0;
493 494 ushort_t stack[ENMAXFILTERS+1];
494 495
495 496 fp = &pfp->pf_Filter[0];
496 497 fpe = pfp->pf_FilterEnd;
497 498
498 499 enprintf(("FilterPacket(%p, %p, %p, %p):\n", pp, pfp, fp, fpe));
499 500
500 501 /*
501 502 * Push TRUE on stack to start. The stack size is chosen such
502 503 * that overflow can't occur -- each operation can push at most
503 504 * one item on the stack, and the stack size equals the maximum
504 505 * program length.
505 506 */
506 507 sp = &stack[ENMAXFILTERS];
507 508 *sp = 1;
508 509
509 510 while (fp < fpe) {
510 511 op = *fp >> ENF_NBPA;
511 512 arg = *fp & ((1 << ENF_NBPA) - 1);
512 513 fp++;
513 514
514 515 switch (arg) {
515 516 default:
516 517 arg -= ENF_PUSHWORD;
517 518 /*
518 519 * Since arg is unsigned,
519 520 * if it were less than ENF_PUSHWORD before,
520 521 * it would now be huge.
521 522 */
522 523 if (arg + offreg < maxhdr)
523 524 *--sp = pp->pd_hdr[arg + offreg];
524 525 else if (arg + offreg < maxword)
525 526 *--sp = pp->pd_body[arg - maxhdr + offreg];
526 527 else {
527 528 enprintf(("=>0(len)\n"));
528 529 return (0);
529 530 }
530 531 break;
531 532 case ENF_PUSHLIT:
532 533 *--sp = *fp++;
533 534 break;
534 535 case ENF_PUSHZERO:
535 536 *--sp = 0;
536 537 break;
537 538 case ENF_PUSHONE:
538 539 *--sp = 1;
↓ open down ↓ |
92 lines elided |
↑ open up ↑ |
539 540 break;
540 541 case ENF_PUSHFFFF:
541 542 *--sp = 0xffff;
542 543 break;
543 544 case ENF_PUSHFF00:
544 545 *--sp = 0xff00;
545 546 break;
546 547 case ENF_PUSH00FF:
547 548 *--sp = 0x00ff;
548 549 break;
550 + case ENF_PUSHFF00_N:
551 + *--sp = htons(0xff00);
552 + break;
553 + case ENF_PUSH00FF_N:
554 + *--sp = htons(0x00ff);
555 + break;
549 556 case ENF_LOAD_OFFSET:
550 557 offreg = *fp++;
551 558 break;
552 559 case ENF_BRTR:
553 560 if (*sp != 0)
554 561 fp += *fp;
555 562 else
556 563 fp++;
557 564 if (fp >= fpe) {
558 565 enprintf(("BRTR: fp>=fpe\n"));
559 566 return (0);
560 567 }
561 568 break;
562 569 case ENF_BRFL:
563 570 if (*sp == 0)
564 571 fp += *fp;
565 572 else
566 573 fp++;
567 574 if (fp >= fpe) {
568 575 enprintf(("BRFL: fp>=fpe\n"));
569 576 return (0);
570 577 }
571 578 break;
572 579 case ENF_POP:
573 580 ++sp;
574 581 if (sp > &stack[ENMAXFILTERS]) {
575 582 enprintf(("stack underflow\n"));
576 583 return (0);
577 584 }
578 585 break;
579 586 case ENF_NOPUSH:
580 587 break;
581 588 }
582 589
583 590 if (sp < &stack[2]) { /* check stack overflow: small yellow zone */
584 591 enprintf(("=>0(--sp)\n"));
585 592 return (0);
586 593 }
587 594
588 595 if (op == ENF_NOP)
589 596 continue;
590 597
591 598 /*
592 599 * all non-NOP operators binary, must have at least two operands
593 600 * on stack to evaluate.
594 601 */
595 602 if (sp > &stack[ENMAXFILTERS-2]) {
596 603 enprintf(("=>0(sp++)\n"));
597 604 return (0);
598 605 }
599 606
600 607 arg = *sp++;
601 608 switch (op) {
602 609 default:
603 610 enprintf(("=>0(def)\n"));
604 611 return (0);
605 612 case opx(ENF_AND):
606 613 *sp &= arg;
607 614 break;
608 615 case opx(ENF_OR):
609 616 *sp |= arg;
610 617 break;
611 618 case opx(ENF_XOR):
612 619 *sp ^= arg;
613 620 break;
614 621 case opx(ENF_EQ):
615 622 *sp = (*sp == arg);
616 623 break;
617 624 case opx(ENF_NEQ):
618 625 *sp = (*sp != arg);
619 626 break;
620 627 case opx(ENF_LT):
621 628 *sp = (*sp < arg);
622 629 break;
623 630 case opx(ENF_LE):
624 631 *sp = (*sp <= arg);
625 632 break;
626 633 case opx(ENF_GT):
627 634 *sp = (*sp > arg);
628 635 break;
629 636 case opx(ENF_GE):
630 637 *sp = (*sp >= arg);
631 638 break;
632 639
633 640 /* short-circuit operators */
634 641
635 642 case opx(ENF_COR):
636 643 if (*sp++ == arg) {
637 644 enprintf(("=>COR %x\n", *sp));
638 645 return (1);
639 646 }
640 647 break;
641 648 case opx(ENF_CAND):
642 649 if (*sp++ != arg) {
643 650 enprintf(("=>CAND %x\n", *sp));
644 651 return (0);
645 652 }
646 653 break;
647 654 case opx(ENF_CNOR):
648 655 if (*sp++ == arg) {
649 656 enprintf(("=>COR %x\n", *sp));
650 657 return (0);
651 658 }
652 659 break;
653 660 case opx(ENF_CNAND):
654 661 if (*sp++ != arg) {
655 662 enprintf(("=>CNAND %x\n", *sp));
656 663 return (1);
657 664 }
658 665 break;
659 666 }
660 667 }
661 668 enprintf(("=>%x\n", *sp));
662 669 return (*sp);
663 670 }
↓ open down ↓ |
105 lines elided |
↑ open up ↑ |
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX