1 /* 2 * Copyright 2002-2004, Instant802 Networks, Inc. 3 * Copyright 2008, Jouni Malinen <j@w1.fi> 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License version 2 as 7 * published by the Free Software Foundation. 8 */ 9 10 #include <linux/netdevice.h> 11 #include <linux/types.h> 12 #include <linux/slab.h> 13 #include <linux/skbuff.h> 14 #include <linux/compiler.h> 15 #include <linux/ieee80211.h> 16 #include <asm/unaligned.h> 17 #include <net/mac80211.h> 18 19 #include "ieee80211_i.h" 20 #include "michael.h" 21 #include "tkip.h" 22 #include "aes_ccm.h" 23 #include "aes_cmac.h" 24 #include "wpa.h" 25 26 ieee80211_tx_result 27 ieee80211_tx_h_michael_mic_add(struct ieee80211_tx_data *tx) 28 { 29 u8 *data, *key, *mic, key_offset; 30 size_t data_len; 31 unsigned int hdrlen; 32 struct ieee80211_hdr *hdr; 33 struct sk_buff *skb = tx->skb; 34 int authenticator; 35 int wpa_test = 0; 36 int tail; 37 38 hdr = (struct ieee80211_hdr *)skb->data; 39 if (!tx->key || tx->key->conf.alg != ALG_TKIP || skb->len < 24 || 40 !ieee80211_is_data_present(hdr->frame_control)) 41 return TX_CONTINUE; 42 43 hdrlen = ieee80211_hdrlen(hdr->frame_control); 44 if (skb->len < hdrlen) 45 return TX_DROP; 46 47 data = skb->data + hdrlen; 48 data_len = skb->len - hdrlen; 49 50 if ((tx->key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE) && 51 !(tx->flags & IEEE80211_TX_FRAGMENTED) && 52 !(tx->key->conf.flags & IEEE80211_KEY_FLAG_GENERATE_MMIC) && 53 !wpa_test) { 54 /* hwaccel - with no need for preallocated room for MMIC */ 55 return TX_CONTINUE; 56 } 57 58 tail = MICHAEL_MIC_LEN; 59 if (!(tx->key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE)) 60 tail += TKIP_ICV_LEN; 61 62 if (WARN_ON(skb_tailroom(skb) < tail || 63 skb_headroom(skb) < TKIP_IV_LEN)) 64 return TX_DROP; 65 66 #if 0 67 authenticator = fc & IEEE80211_FCTL_FROMDS; /* FIX */ 68 #else 69 authenticator = 1; 70 #endif 71 key_offset = authenticator ? 72 NL80211_TKIP_DATA_OFFSET_TX_MIC_KEY : 73 NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY; 74 key = &tx->key->conf.key[key_offset]; 75 mic = skb_put(skb, MICHAEL_MIC_LEN); 76 michael_mic(key, hdr, data, data_len, mic); 77 78 return TX_CONTINUE; 79 } 80 81 82 ieee80211_rx_result 83 ieee80211_rx_h_michael_mic_verify(struct ieee80211_rx_data *rx) 84 { 85 u8 *data, *key = NULL, key_offset; 86 size_t data_len; 87 unsigned int hdrlen; 88 struct ieee80211_hdr *hdr; 89 u8 mic[MICHAEL_MIC_LEN]; 90 struct sk_buff *skb = rx->skb; 91 int authenticator = 1, wpa_test = 0; 92 93 /* No way to verify the MIC if the hardware stripped it */ 94 if (rx->status->flag & RX_FLAG_MMIC_STRIPPED) 95 return RX_CONTINUE; 96 97 hdr = (struct ieee80211_hdr *)skb->data; 98 if (!rx->key || rx->key->conf.alg != ALG_TKIP || 99 !ieee80211_has_protected(hdr->frame_control) || 100 !ieee80211_is_data_present(hdr->frame_control)) 101 return RX_CONTINUE; 102 103 hdrlen = ieee80211_hdrlen(hdr->frame_control); 104 if (skb->len < hdrlen + MICHAEL_MIC_LEN) 105 return RX_DROP_UNUSABLE; 106 107 data = skb->data + hdrlen; 108 data_len = skb->len - hdrlen - MICHAEL_MIC_LEN; 109 110 #if 0 111 authenticator = fc & IEEE80211_FCTL_TODS; /* FIX */ 112 #else 113 authenticator = 1; 114 #endif 115 key_offset = authenticator ? 116 NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY : 117 NL80211_TKIP_DATA_OFFSET_TX_MIC_KEY; 118 key = &rx->key->conf.key[key_offset]; 119 michael_mic(key, hdr, data, data_len, mic); 120 if (memcmp(mic, data + data_len, MICHAEL_MIC_LEN) != 0 || wpa_test) { 121 if (!(rx->flags & IEEE80211_RX_RA_MATCH)) 122 return RX_DROP_UNUSABLE; 123 124 mac80211_ev_michael_mic_failure(rx->sdata, rx->key->conf.keyidx, 125 (void *) skb->data); 126 return RX_DROP_UNUSABLE; 127 } 128 129 /* remove Michael MIC from payload */ 130 skb_trim(skb, skb->len - MICHAEL_MIC_LEN); 131 132 /* update IV in key information to be able to detect replays */ 133 rx->key->u.tkip.rx[rx->queue].iv32 = rx->tkip_iv32; 134 rx->key->u.tkip.rx[rx->queue].iv16 = rx->tkip_iv16; 135 136 return RX_CONTINUE; 137 } 138 139 140 static int tkip_encrypt_skb(struct ieee80211_tx_data *tx, struct sk_buff *skb) 141 { 142 struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data; 143 struct ieee80211_key *key = tx->key; 144 struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); 145 unsigned int hdrlen; 146 int len, tail; 147 u8 *pos; 148 149 if ((tx->key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE) && 150 !(tx->key->conf.flags & IEEE80211_KEY_FLAG_GENERATE_IV)) { 151 /* hwaccel - with no need for preallocated room for IV/ICV */ 152 info->control.hw_key = &tx->key->conf; 153 return 0; 154 } 155 156 hdrlen = ieee80211_hdrlen(hdr->frame_control); 157 len = skb->len - hdrlen; 158 159 if (tx->key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE) 160 tail = 0; 161 else 162 tail = TKIP_ICV_LEN; 163 164 if (WARN_ON(skb_tailroom(skb) < tail || 165 skb_headroom(skb) < TKIP_IV_LEN)) 166 return -1; 167 168 pos = skb_push(skb, TKIP_IV_LEN); 169 memmove(pos, pos + TKIP_IV_LEN, hdrlen); 170 pos += hdrlen; 171 172 /* Increase IV for the frame */ 173 key->u.tkip.tx.iv16++; 174 if (key->u.tkip.tx.iv16 == 0) 175 key->u.tkip.tx.iv32++; 176 177 if (tx->key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE) { 178 /* hwaccel - with preallocated room for IV */ 179 ieee80211_tkip_add_iv(pos, key, key->u.tkip.tx.iv16); 180 181 info->control.hw_key = &tx->key->conf; 182 return 0; 183 } 184 185 /* Add room for ICV */ 186 skb_put(skb, TKIP_ICV_LEN); 187 188 hdr = (struct ieee80211_hdr *) skb->data; 189 ieee80211_tkip_encrypt_data(tx->local->wep_tx_tfm, 190 key, pos, len, hdr->addr2); 191 return 0; 192 } 193 194 195 ieee80211_tx_result 196 ieee80211_crypto_tkip_encrypt(struct ieee80211_tx_data *tx) 197 { 198 struct sk_buff *skb = tx->skb; 199 200 ieee80211_tx_set_protected(tx); 201 202 do { 203 if (tkip_encrypt_skb(tx, skb) < 0) 204 return TX_DROP; 205 } while ((skb = skb->next)); 206 207 return TX_CONTINUE; 208 } 209 210 211 ieee80211_rx_result 212 ieee80211_crypto_tkip_decrypt(struct ieee80211_rx_data *rx) 213 { 214 struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) rx->skb->data; 215 int hdrlen, res, hwaccel = 0, wpa_test = 0; 216 struct ieee80211_key *key = rx->key; 217 struct sk_buff *skb = rx->skb; 218 219 hdrlen = ieee80211_hdrlen(hdr->frame_control); 220 221 if (!ieee80211_is_data(hdr->frame_control)) 222 return RX_CONTINUE; 223 224 if (!rx->sta || skb->len - hdrlen < 12) 225 return RX_DROP_UNUSABLE; 226 227 if (rx->status->flag & RX_FLAG_DECRYPTED) { 228 if (rx->status->flag & RX_FLAG_IV_STRIPPED) { 229 /* 230 * Hardware took care of all processing, including 231 * replay protection, and stripped the ICV/IV so 232 * we cannot do any checks here. 233 */ 234 return RX_CONTINUE; 235 } 236 237 /* let TKIP code verify IV, but skip decryption */ 238 hwaccel = 1; 239 } 240 241 res = ieee80211_tkip_decrypt_data(rx->local->wep_rx_tfm, 242 key, skb->data + hdrlen, 243 skb->len - hdrlen, rx->sta->sta.addr, 244 hdr->addr1, hwaccel, rx->queue, 245 &rx->tkip_iv32, 246 &rx->tkip_iv16); 247 if (res != TKIP_DECRYPT_OK || wpa_test) 248 return RX_DROP_UNUSABLE; 249 250 /* Trim ICV */ 251 skb_trim(skb, skb->len - TKIP_ICV_LEN); 252 253 /* Remove IV */ 254 memmove(skb->data + TKIP_IV_LEN, skb->data, hdrlen); 255 skb_pull(skb, TKIP_IV_LEN); 256 257 return RX_CONTINUE; 258 } 259 260 261 static void ccmp_special_blocks(struct sk_buff *skb, u8 *pn, u8 *scratch, 262 int encrypted) 263 { 264 __le16 mask_fc; 265 int a4_included, mgmt; 266 u8 qos_tid; 267 u8 *b_0, *aad; 268 u16 data_len, len_a; 269 unsigned int hdrlen; 270 struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; 271 272 b_0 = scratch + 3 * AES_BLOCK_LEN; 273 aad = scratch + 4 * AES_BLOCK_LEN; 274 275 /* 276 * Mask FC: zero subtype b4 b5 b6 (if not mgmt) 277 * Retry, PwrMgt, MoreData; set Protected 278 */ 279 mgmt = ieee80211_is_mgmt(hdr->frame_control); 280 mask_fc = hdr->frame_control; 281 mask_fc &= ~cpu_to_le16(IEEE80211_FCTL_RETRY | 282 IEEE80211_FCTL_PM | IEEE80211_FCTL_MOREDATA); 283 if (!mgmt) 284 mask_fc &= ~cpu_to_le16(0x0070); 285 mask_fc |= cpu_to_le16(IEEE80211_FCTL_PROTECTED); 286 287 hdrlen = ieee80211_hdrlen(hdr->frame_control); 288 len_a = hdrlen - 2; 289 a4_included = ieee80211_has_a4(hdr->frame_control); 290 291 if (ieee80211_is_data_qos(hdr->frame_control)) 292 qos_tid = *ieee80211_get_qos_ctl(hdr) & IEEE80211_QOS_CTL_TID_MASK; 293 else 294 qos_tid = 0; 295 296 data_len = skb->len - hdrlen - CCMP_HDR_LEN; 297 if (encrypted) 298 data_len -= CCMP_MIC_LEN; 299 300 /* First block, b_0 */ 301 b_0[0] = 0x59; /* flags: Adata: 1, M: 011, L: 001 */ 302 /* Nonce: Nonce Flags | A2 | PN 303 * Nonce Flags: Priority (b0..b3) | Management (b4) | Reserved (b5..b7) 304 */ 305 b_0[1] = qos_tid | (mgmt << 4); 306 memcpy(&b_0[2], hdr->addr2, ETH_ALEN); 307 memcpy(&b_0[8], pn, CCMP_PN_LEN); 308 /* l(m) */ 309 put_unaligned_be16(data_len, &b_0[14]); 310 311 /* AAD (extra authenticate-only data) / masked 802.11 header 312 * FC | A1 | A2 | A3 | SC | [A4] | [QC] */ 313 put_unaligned_be16(len_a, &aad[0]); 314 put_unaligned(mask_fc, (__le16 *)&aad[2]); 315 memcpy(&aad[4], &hdr->addr1, 3 * ETH_ALEN); 316 317 /* Mask Seq#, leave Frag# */ 318 aad[22] = *((u8 *) &hdr->seq_ctrl) & 0x0f; 319 aad[23] = 0; 320 321 if (a4_included) { 322 memcpy(&aad[24], hdr->addr4, ETH_ALEN); 323 aad[30] = qos_tid; 324 aad[31] = 0; 325 } else { 326 memset(&aad[24], 0, ETH_ALEN + IEEE80211_QOS_CTL_LEN); 327 aad[24] = qos_tid; 328 } 329 } 330 331 332 static inline void ccmp_pn2hdr(u8 *hdr, u8 *pn, int key_id) 333 { 334 hdr[0] = pn[5]; 335 hdr[1] = pn[4]; 336 hdr[2] = 0; 337 hdr[3] = 0x20 | (key_id << 6); 338 hdr[4] = pn[3]; 339 hdr[5] = pn[2]; 340 hdr[6] = pn[1]; 341 hdr[7] = pn[0]; 342 } 343 344 345 static inline void ccmp_hdr2pn(u8 *pn, u8 *hdr) 346 { 347 pn[0] = hdr[7]; 348 pn[1] = hdr[6]; 349 pn[2] = hdr[5]; 350 pn[3] = hdr[4]; 351 pn[4] = hdr[1]; 352 pn[5] = hdr[0]; 353 } 354 355 356 static int ccmp_encrypt_skb(struct ieee80211_tx_data *tx, struct sk_buff *skb) 357 { 358 struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data; 359 struct ieee80211_key *key = tx->key; 360 struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); 361 int hdrlen, len, tail; 362 u8 *pos, *pn; 363 int i; 364 bool skip_hw; 365 366 skip_hw = (tx->key->conf.flags & IEEE80211_KEY_FLAG_SW_MGMT) && 367 ieee80211_is_mgmt(hdr->frame_control); 368 369 if ((tx->key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE) && 370 !(tx->key->conf.flags & IEEE80211_KEY_FLAG_GENERATE_IV) && 371 !skip_hw) { 372 /* hwaccel - with no need for preallocated room for CCMP 373 * header or MIC fields */ 374 info->control.hw_key = &tx->key->conf; 375 return 0; 376 } 377 378 hdrlen = ieee80211_hdrlen(hdr->frame_control); 379 len = skb->len - hdrlen; 380 381 if (key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE) 382 tail = 0; 383 else 384 tail = CCMP_MIC_LEN; 385 386 if (WARN_ON(skb_tailroom(skb) < tail || 387 skb_headroom(skb) < CCMP_HDR_LEN)) 388 return -1; 389 390 pos = skb_push(skb, CCMP_HDR_LEN); 391 memmove(pos, pos + CCMP_HDR_LEN, hdrlen); 392 hdr = (struct ieee80211_hdr *) pos; 393 pos += hdrlen; 394 395 /* PN = PN + 1 */ 396 pn = key->u.ccmp.tx_pn; 397 398 for (i = CCMP_PN_LEN - 1; i >= 0; i--) { 399 pn[i]++; 400 if (pn[i]) 401 break; 402 } 403 404 ccmp_pn2hdr(pos, pn, key->conf.keyidx); 405 406 if ((key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE) && !skip_hw) { 407 /* hwaccel - with preallocated room for CCMP header */ 408 info->control.hw_key = &tx->key->conf; 409 return 0; 410 } 411 412 pos += CCMP_HDR_LEN; 413 ccmp_special_blocks(skb, pn, key->u.ccmp.tx_crypto_buf, 0); 414 ieee80211_aes_ccm_encrypt(key->u.ccmp.tfm, key->u.ccmp.tx_crypto_buf, pos, len, 415 pos, skb_put(skb, CCMP_MIC_LEN)); 416 417 return 0; 418 } 419 420 421 ieee80211_tx_result 422 ieee80211_crypto_ccmp_encrypt(struct ieee80211_tx_data *tx) 423 { 424 struct sk_buff *skb = tx->skb; 425 426 ieee80211_tx_set_protected(tx); 427 428 do { 429 if (ccmp_encrypt_skb(tx, skb) < 0) 430 return TX_DROP; 431 } while ((skb = skb->next)); 432 433 return TX_CONTINUE; 434 } 435 436 437 ieee80211_rx_result 438 ieee80211_crypto_ccmp_decrypt(struct ieee80211_rx_data *rx) 439 { 440 struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)rx->skb->data; 441 int hdrlen; 442 struct ieee80211_key *key = rx->key; 443 struct sk_buff *skb = rx->skb; 444 u8 pn[CCMP_PN_LEN]; 445 int data_len; 446 447 hdrlen = ieee80211_hdrlen(hdr->frame_control); 448 449 if (!ieee80211_is_data(hdr->frame_control) && 450 !ieee80211_is_robust_mgmt_frame(hdr)) 451 return RX_CONTINUE; 452 453 data_len = skb->len - hdrlen - CCMP_HDR_LEN - CCMP_MIC_LEN; 454 if (!rx->sta || data_len < 0) 455 return RX_DROP_UNUSABLE; 456 457 if ((rx->status->flag & RX_FLAG_DECRYPTED) && 458 (rx->status->flag & RX_FLAG_IV_STRIPPED)) 459 return RX_CONTINUE; 460 461 ccmp_hdr2pn(pn, skb->data + hdrlen); 462 463 if (memcmp(pn, key->u.ccmp.rx_pn[rx->queue], CCMP_PN_LEN) <= 0) { 464 key->u.ccmp.replays++; 465 return RX_DROP_UNUSABLE; 466 } 467 468 if (!(rx->status->flag & RX_FLAG_DECRYPTED)) { 469 /* hardware didn't decrypt/verify MIC */ 470 ccmp_special_blocks(skb, pn, key->u.ccmp.rx_crypto_buf, 1); 471 472 if (ieee80211_aes_ccm_decrypt( 473 key->u.ccmp.tfm, key->u.ccmp.rx_crypto_buf, 474 skb->data + hdrlen + CCMP_HDR_LEN, data_len, 475 skb->data + skb->len - CCMP_MIC_LEN, 476 skb->data + hdrlen + CCMP_HDR_LEN)) 477 return RX_DROP_UNUSABLE; 478 } 479 480 memcpy(key->u.ccmp.rx_pn[rx->queue], pn, CCMP_PN_LEN); 481 482 /* Remove CCMP header and MIC */ 483 skb_trim(skb, skb->len - CCMP_MIC_LEN); 484 memmove(skb->data + CCMP_HDR_LEN, skb->data, hdrlen); 485 skb_pull(skb, CCMP_HDR_LEN); 486 487 return RX_CONTINUE; 488 } 489 490 491 static void bip_aad(struct sk_buff *skb, u8 *aad) 492 { 493 /* BIP AAD: FC(masked) || A1 || A2 || A3 */ 494 495 /* FC type/subtype */ 496 aad[0] = skb->data[0]; 497 /* Mask FC Retry, PwrMgt, MoreData flags to zero */ 498 aad[1] = skb->data[1] & ~(BIT(4) | BIT(5) | BIT(6)); 499 /* A1 || A2 || A3 */ 500 memcpy(aad + 2, skb->data + 4, 3 * ETH_ALEN); 501 } 502 503 504 static inline void bip_ipn_swap(u8 *d, const u8 *s) 505 { 506 *d++ = s[5]; 507 *d++ = s[4]; 508 *d++ = s[3]; 509 *d++ = s[2]; 510 *d++ = s[1]; 511 *d = s[0]; 512 } 513 514 515 ieee80211_tx_result 516 ieee80211_crypto_aes_cmac_encrypt(struct ieee80211_tx_data *tx) 517 { 518 struct sk_buff *skb = tx->skb; 519 struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); 520 struct ieee80211_key *key = tx->key; 521 struct ieee80211_mmie *mmie; 522 u8 *pn, aad[20]; 523 int i; 524 525 if (tx->key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE) { 526 /* hwaccel */ 527 info->control.hw_key = &tx->key->conf; 528 return 0; 529 } 530 531 if (WARN_ON(skb_tailroom(skb) < sizeof(*mmie))) 532 return TX_DROP; 533 534 mmie = (struct ieee80211_mmie *) skb_put(skb, sizeof(*mmie)); 535 mmie->element_id = WLAN_EID_MMIE; 536 mmie->length = sizeof(*mmie) - 2; 537 mmie->key_id = cpu_to_le16(key->conf.keyidx); 538 539 /* PN = PN + 1 */ 540 pn = key->u.aes_cmac.tx_pn; 541 542 for (i = sizeof(key->u.aes_cmac.tx_pn) - 1; i >= 0; i--) { 543 pn[i]++; 544 if (pn[i]) 545 break; 546 } 547 bip_ipn_swap(mmie->sequence_number, pn); 548 549 bip_aad(skb, aad); 550 551 /* 552 * MIC = AES-128-CMAC(IGTK, AAD || Management Frame Body || MMIE, 64) 553 */ 554 ieee80211_aes_cmac(key->u.aes_cmac.tfm, key->u.aes_cmac.tx_crypto_buf, 555 aad, skb->data + 24, skb->len - 24, mmie->mic); 556 557 return TX_CONTINUE; 558 } 559 560 561 ieee80211_rx_result 562 ieee80211_crypto_aes_cmac_decrypt(struct ieee80211_rx_data *rx) 563 { 564 struct sk_buff *skb = rx->skb; 565 struct ieee80211_key *key = rx->key; 566 struct ieee80211_mmie *mmie; 567 u8 aad[20], mic[8], ipn[6]; 568 struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data; 569 570 if (!ieee80211_is_mgmt(hdr->frame_control)) 571 return RX_CONTINUE; 572 573 if ((rx->status->flag & RX_FLAG_DECRYPTED) && 574 (rx->status->flag & RX_FLAG_IV_STRIPPED)) 575 return RX_CONTINUE; 576 577 if (skb->len < 24 + sizeof(*mmie)) 578 return RX_DROP_UNUSABLE; 579 580 mmie = (struct ieee80211_mmie *) 581 (skb->data + skb->len - sizeof(*mmie)); 582 if (mmie->element_id != WLAN_EID_MMIE || 583 mmie->length != sizeof(*mmie) - 2) 584 return RX_DROP_UNUSABLE; /* Invalid MMIE */ 585 586 bip_ipn_swap(ipn, mmie->sequence_number); 587 588 if (memcmp(ipn, key->u.aes_cmac.rx_pn, 6) <= 0) { 589 key->u.aes_cmac.replays++; 590 return RX_DROP_UNUSABLE; 591 } 592 593 if (!(rx->status->flag & RX_FLAG_DECRYPTED)) { 594 /* hardware didn't decrypt/verify MIC */ 595 bip_aad(skb, aad); 596 ieee80211_aes_cmac(key->u.aes_cmac.tfm, 597 key->u.aes_cmac.rx_crypto_buf, aad, 598 skb->data + 24, skb->len - 24, mic); 599 if (memcmp(mic, mmie->mic, sizeof(mmie->mic)) != 0) { 600 key->u.aes_cmac.icverrors++; 601 return RX_DROP_UNUSABLE; 602 } 603 } 604 605 memcpy(key->u.aes_cmac.rx_pn, ipn, 6); 606 607 /* Remove MMIE */ 608 skb_trim(skb, skb->len - sizeof(*mmie)); 609 610 return RX_CONTINUE; 611 } 612