diff options
Diffstat (limited to 'net/mac80211/rx.c')
-rw-r--r-- | net/mac80211/rx.c | 350 |
1 files changed, 226 insertions, 124 deletions
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index cb44a9db0e19..a0dfafb4f38b 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c | |||
@@ -25,6 +25,207 @@ | |||
25 | #include "tkip.h" | 25 | #include "tkip.h" |
26 | #include "wme.h" | 26 | #include "wme.h" |
27 | 27 | ||
28 | /* | ||
29 | * monitor mode reception | ||
30 | * | ||
31 | * This function cleans up the SKB, i.e. it removes all the stuff | ||
32 | * only useful for monitoring. | ||
33 | */ | ||
34 | static struct sk_buff *remove_monitor_info(struct ieee80211_local *local, | ||
35 | struct sk_buff *skb, | ||
36 | int rtap_len) | ||
37 | { | ||
38 | skb_pull(skb, rtap_len); | ||
39 | |||
40 | if (local->hw.flags & IEEE80211_HW_RX_INCLUDES_FCS) { | ||
41 | if (likely(skb->len > FCS_LEN)) | ||
42 | skb_trim(skb, skb->len - FCS_LEN); | ||
43 | else { | ||
44 | /* driver bug */ | ||
45 | WARN_ON(1); | ||
46 | dev_kfree_skb(skb); | ||
47 | skb = NULL; | ||
48 | } | ||
49 | } | ||
50 | |||
51 | return skb; | ||
52 | } | ||
53 | |||
54 | static inline int should_drop_frame(struct ieee80211_rx_status *status, | ||
55 | struct sk_buff *skb, | ||
56 | int present_fcs_len, | ||
57 | int radiotap_len) | ||
58 | { | ||
59 | struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data; | ||
60 | |||
61 | if (status->flag & (RX_FLAG_FAILED_FCS_CRC | RX_FLAG_FAILED_PLCP_CRC)) | ||
62 | return 1; | ||
63 | if (unlikely(skb->len < 16 + present_fcs_len + radiotap_len)) | ||
64 | return 1; | ||
65 | if ((hdr->frame_control & cpu_to_le16(IEEE80211_FCTL_FTYPE)) == | ||
66 | cpu_to_le16(IEEE80211_FTYPE_CTL)) | ||
67 | return 1; | ||
68 | return 0; | ||
69 | } | ||
70 | |||
71 | /* | ||
72 | * This function copies a received frame to all monitor interfaces and | ||
73 | * returns a cleaned-up SKB that no longer includes the FCS nor the | ||
74 | * radiotap header the driver might have added. | ||
75 | */ | ||
76 | static struct sk_buff * | ||
77 | ieee80211_rx_monitor(struct ieee80211_local *local, struct sk_buff *origskb, | ||
78 | struct ieee80211_rx_status *status) | ||
79 | { | ||
80 | struct ieee80211_sub_if_data *sdata; | ||
81 | struct ieee80211_rate *rate; | ||
82 | int needed_headroom = 0; | ||
83 | struct ieee80211_rtap_hdr { | ||
84 | struct ieee80211_radiotap_header hdr; | ||
85 | u8 flags; | ||
86 | u8 rate; | ||
87 | __le16 chan_freq; | ||
88 | __le16 chan_flags; | ||
89 | u8 antsignal; | ||
90 | u8 padding_for_rxflags; | ||
91 | __le16 rx_flags; | ||
92 | } __attribute__ ((packed)) *rthdr; | ||
93 | struct sk_buff *skb, *skb2; | ||
94 | struct net_device *prev_dev = NULL; | ||
95 | int present_fcs_len = 0; | ||
96 | int rtap_len = 0; | ||
97 | |||
98 | /* | ||
99 | * First, we may need to make a copy of the skb because | ||
100 | * (1) we need to modify it for radiotap (if not present), and | ||
101 | * (2) the other RX handlers will modify the skb we got. | ||
102 | * | ||
103 | * We don't need to, of course, if we aren't going to return | ||
104 | * the SKB because it has a bad FCS/PLCP checksum. | ||
105 | */ | ||
106 | if (status->flag & RX_FLAG_RADIOTAP) | ||
107 | rtap_len = ieee80211_get_radiotap_len(origskb->data); | ||
108 | else | ||
109 | needed_headroom = sizeof(*rthdr); | ||
110 | |||
111 | if (local->hw.flags & IEEE80211_HW_RX_INCLUDES_FCS) | ||
112 | present_fcs_len = FCS_LEN; | ||
113 | |||
114 | if (!local->monitors) { | ||
115 | if (should_drop_frame(status, origskb, present_fcs_len, | ||
116 | rtap_len)) { | ||
117 | dev_kfree_skb(origskb); | ||
118 | return NULL; | ||
119 | } | ||
120 | |||
121 | return remove_monitor_info(local, origskb, rtap_len); | ||
122 | } | ||
123 | |||
124 | if (should_drop_frame(status, origskb, present_fcs_len, rtap_len)) { | ||
125 | /* only need to expand headroom if necessary */ | ||
126 | skb = origskb; | ||
127 | origskb = NULL; | ||
128 | |||
129 | /* | ||
130 | * This shouldn't trigger often because most devices have an | ||
131 | * RX header they pull before we get here, and that should | ||
132 | * be big enough for our radiotap information. We should | ||
133 | * probably export the length to drivers so that we can have | ||
134 | * them allocate enough headroom to start with. | ||
135 | */ | ||
136 | if (skb_headroom(skb) < needed_headroom && | ||
137 | pskb_expand_head(skb, sizeof(*rthdr), 0, GFP_ATOMIC)) { | ||
138 | dev_kfree_skb(skb); | ||
139 | return NULL; | ||
140 | } | ||
141 | } else { | ||
142 | /* | ||
143 | * Need to make a copy and possibly remove radiotap header | ||
144 | * and FCS from the original. | ||
145 | */ | ||
146 | skb = skb_copy_expand(origskb, needed_headroom, 0, GFP_ATOMIC); | ||
147 | |||
148 | origskb = remove_monitor_info(local, origskb, rtap_len); | ||
149 | |||
150 | if (!skb) | ||
151 | return origskb; | ||
152 | } | ||
153 | |||
154 | /* if necessary, prepend radiotap information */ | ||
155 | if (!(status->flag & RX_FLAG_RADIOTAP)) { | ||
156 | rthdr = (void *) skb_push(skb, sizeof(*rthdr)); | ||
157 | memset(rthdr, 0, sizeof(*rthdr)); | ||
158 | rthdr->hdr.it_len = cpu_to_le16(sizeof(*rthdr)); | ||
159 | rthdr->hdr.it_present = | ||
160 | cpu_to_le32((1 << IEEE80211_RADIOTAP_FLAGS) | | ||
161 | (1 << IEEE80211_RADIOTAP_RATE) | | ||
162 | (1 << IEEE80211_RADIOTAP_CHANNEL) | | ||
163 | (1 << IEEE80211_RADIOTAP_DB_ANTSIGNAL) | | ||
164 | (1 << IEEE80211_RADIOTAP_RX_FLAGS)); | ||
165 | rthdr->flags = local->hw.flags & IEEE80211_HW_RX_INCLUDES_FCS ? | ||
166 | IEEE80211_RADIOTAP_F_FCS : 0; | ||
167 | |||
168 | /* FIXME: when radiotap gets a 'bad PLCP' flag use it here */ | ||
169 | rthdr->rx_flags = 0; | ||
170 | if (status->flag & | ||
171 | (RX_FLAG_FAILED_FCS_CRC | RX_FLAG_FAILED_PLCP_CRC)) | ||
172 | rthdr->rx_flags |= | ||
173 | cpu_to_le16(IEEE80211_RADIOTAP_F_RX_BADFCS); | ||
174 | |||
175 | rate = ieee80211_get_rate(local, status->phymode, | ||
176 | status->rate); | ||
177 | if (rate) | ||
178 | rthdr->rate = rate->rate / 5; | ||
179 | |||
180 | rthdr->chan_freq = cpu_to_le16(status->freq); | ||
181 | |||
182 | if (status->phymode == MODE_IEEE80211A) | ||
183 | rthdr->chan_flags = | ||
184 | cpu_to_le16(IEEE80211_CHAN_OFDM | | ||
185 | IEEE80211_CHAN_5GHZ); | ||
186 | else | ||
187 | rthdr->chan_flags = | ||
188 | cpu_to_le16(IEEE80211_CHAN_DYN | | ||
189 | IEEE80211_CHAN_2GHZ); | ||
190 | |||
191 | rthdr->antsignal = status->ssi; | ||
192 | } | ||
193 | |||
194 | skb_set_mac_header(skb, 0); | ||
195 | skb->ip_summed = CHECKSUM_UNNECESSARY; | ||
196 | skb->pkt_type = PACKET_OTHERHOST; | ||
197 | skb->protocol = htons(ETH_P_802_2); | ||
198 | |||
199 | list_for_each_entry_rcu(sdata, &local->interfaces, list) { | ||
200 | if (!netif_running(sdata->dev)) | ||
201 | continue; | ||
202 | |||
203 | if (sdata->type != IEEE80211_IF_TYPE_MNTR) | ||
204 | continue; | ||
205 | |||
206 | if (prev_dev) { | ||
207 | skb2 = skb_clone(skb, GFP_ATOMIC); | ||
208 | if (skb2) { | ||
209 | skb2->dev = prev_dev; | ||
210 | netif_rx(skb2); | ||
211 | } | ||
212 | } | ||
213 | |||
214 | prev_dev = sdata->dev; | ||
215 | sdata->dev->stats.rx_packets++; | ||
216 | sdata->dev->stats.rx_bytes += skb->len; | ||
217 | } | ||
218 | |||
219 | if (prev_dev) { | ||
220 | skb->dev = prev_dev; | ||
221 | netif_rx(skb); | ||
222 | } else | ||
223 | dev_kfree_skb(skb); | ||
224 | |||
225 | return origskb; | ||
226 | } | ||
227 | |||
228 | |||
28 | /* pre-rx handlers | 229 | /* pre-rx handlers |
29 | * | 230 | * |
30 | * these don't have dev/sdata fields in the rx data | 231 | * these don't have dev/sdata fields in the rx data |
@@ -132,100 +333,6 @@ ieee80211_rx_h_if_stats(struct ieee80211_txrx_data *rx) | |||
132 | return TXRX_CONTINUE; | 333 | return TXRX_CONTINUE; |
133 | } | 334 | } |
134 | 335 | ||
135 | static void | ||
136 | ieee80211_rx_monitor(struct net_device *dev, struct sk_buff *skb, | ||
137 | struct ieee80211_rx_status *status) | ||
138 | { | ||
139 | struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); | ||
140 | struct ieee80211_rate *rate; | ||
141 | struct ieee80211_rtap_hdr { | ||
142 | struct ieee80211_radiotap_header hdr; | ||
143 | u8 flags; | ||
144 | u8 rate; | ||
145 | __le16 chan_freq; | ||
146 | __le16 chan_flags; | ||
147 | u8 antsignal; | ||
148 | u8 padding_for_rxflags; | ||
149 | __le16 rx_flags; | ||
150 | } __attribute__ ((packed)) *rthdr; | ||
151 | |||
152 | skb->dev = dev; | ||
153 | |||
154 | if (status->flag & RX_FLAG_RADIOTAP) | ||
155 | goto out; | ||
156 | |||
157 | if (skb_headroom(skb) < sizeof(*rthdr)) { | ||
158 | I802_DEBUG_INC(local->rx_expand_skb_head); | ||
159 | if (pskb_expand_head(skb, sizeof(*rthdr), 0, GFP_ATOMIC)) { | ||
160 | dev_kfree_skb(skb); | ||
161 | return; | ||
162 | } | ||
163 | } | ||
164 | |||
165 | rthdr = (struct ieee80211_rtap_hdr *) skb_push(skb, sizeof(*rthdr)); | ||
166 | memset(rthdr, 0, sizeof(*rthdr)); | ||
167 | rthdr->hdr.it_len = cpu_to_le16(sizeof(*rthdr)); | ||
168 | rthdr->hdr.it_present = | ||
169 | cpu_to_le32((1 << IEEE80211_RADIOTAP_FLAGS) | | ||
170 | (1 << IEEE80211_RADIOTAP_RATE) | | ||
171 | (1 << IEEE80211_RADIOTAP_CHANNEL) | | ||
172 | (1 << IEEE80211_RADIOTAP_DB_ANTSIGNAL) | | ||
173 | (1 << IEEE80211_RADIOTAP_RX_FLAGS)); | ||
174 | rthdr->flags = local->hw.flags & IEEE80211_HW_RX_INCLUDES_FCS ? | ||
175 | IEEE80211_RADIOTAP_F_FCS : 0; | ||
176 | |||
177 | /* FIXME: when radiotap gets a 'bad PLCP' flag use it here */ | ||
178 | rthdr->rx_flags = 0; | ||
179 | if (status->flag & | ||
180 | (RX_FLAG_FAILED_FCS_CRC | RX_FLAG_FAILED_PLCP_CRC)) | ||
181 | rthdr->rx_flags |= cpu_to_le16(IEEE80211_RADIOTAP_F_RX_BADFCS); | ||
182 | |||
183 | rate = ieee80211_get_rate(local, status->phymode, status->rate); | ||
184 | if (rate) | ||
185 | rthdr->rate = rate->rate / 5; | ||
186 | |||
187 | rthdr->chan_freq = cpu_to_le16(status->freq); | ||
188 | rthdr->chan_flags = | ||
189 | status->phymode == MODE_IEEE80211A ? | ||
190 | cpu_to_le16(IEEE80211_CHAN_OFDM | IEEE80211_CHAN_5GHZ) : | ||
191 | cpu_to_le16(IEEE80211_CHAN_DYN | IEEE80211_CHAN_2GHZ); | ||
192 | rthdr->antsignal = status->ssi; | ||
193 | |||
194 | out: | ||
195 | dev->stats.rx_packets++; | ||
196 | dev->stats.rx_bytes += skb->len; | ||
197 | |||
198 | skb_set_mac_header(skb, 0); | ||
199 | skb->ip_summed = CHECKSUM_UNNECESSARY; | ||
200 | skb->pkt_type = PACKET_OTHERHOST; | ||
201 | skb->protocol = htons(ETH_P_802_2); | ||
202 | memset(skb->cb, 0, sizeof(skb->cb)); | ||
203 | netif_rx(skb); | ||
204 | } | ||
205 | |||
206 | static ieee80211_txrx_result | ||
207 | ieee80211_rx_h_monitor(struct ieee80211_txrx_data *rx) | ||
208 | { | ||
209 | if (rx->sdata->type == IEEE80211_IF_TYPE_MNTR) { | ||
210 | ieee80211_rx_monitor(rx->dev, rx->skb, rx->u.rx.status); | ||
211 | return TXRX_QUEUED; | ||
212 | } | ||
213 | |||
214 | /* | ||
215 | * Drop frames with failed FCS/PLCP checksums here, they are only | ||
216 | * relevant for monitor mode, the rest of the stack should never | ||
217 | * see them. | ||
218 | */ | ||
219 | if (rx->u.rx.status->flag & | ||
220 | (RX_FLAG_FAILED_FCS_CRC | RX_FLAG_FAILED_PLCP_CRC)) | ||
221 | return TXRX_DROP; | ||
222 | |||
223 | if (rx->u.rx.status->flag & RX_FLAG_RADIOTAP) | ||
224 | skb_pull(rx->skb, ieee80211_get_radiotap_len(rx->skb->data)); | ||
225 | |||
226 | return TXRX_CONTINUE; | ||
227 | } | ||
228 | |||
229 | static ieee80211_txrx_result | 336 | static ieee80211_txrx_result |
230 | ieee80211_rx_h_passive_scan(struct ieee80211_txrx_data *rx) | 337 | ieee80211_rx_h_passive_scan(struct ieee80211_txrx_data *rx) |
231 | { | 338 | { |
@@ -266,10 +373,6 @@ ieee80211_rx_h_check(struct ieee80211_txrx_data *rx) | |||
266 | rx->sta->last_seq_ctrl[rx->u.rx.queue] = hdr->seq_ctrl; | 373 | rx->sta->last_seq_ctrl[rx->u.rx.queue] = hdr->seq_ctrl; |
267 | } | 374 | } |
268 | 375 | ||
269 | if ((rx->local->hw.flags & IEEE80211_HW_RX_INCLUDES_FCS) && | ||
270 | rx->skb->len > FCS_LEN) | ||
271 | skb_trim(rx->skb, rx->skb->len - FCS_LEN); | ||
272 | |||
273 | if (unlikely(rx->skb->len < 16)) { | 376 | if (unlikely(rx->skb->len < 16)) { |
274 | I802_DEBUG_INC(rx->local->rx_handlers_drop_short); | 377 | I802_DEBUG_INC(rx->local->rx_handlers_drop_short); |
275 | return TXRX_DROP; | 378 | return TXRX_DROP; |
@@ -1264,7 +1367,6 @@ static void ieee80211_rx_michael_mic_report(struct net_device *dev, | |||
1264 | ieee80211_rx_handler ieee80211_rx_handlers[] = | 1367 | ieee80211_rx_handler ieee80211_rx_handlers[] = |
1265 | { | 1368 | { |
1266 | ieee80211_rx_h_if_stats, | 1369 | ieee80211_rx_h_if_stats, |
1267 | ieee80211_rx_h_monitor, | ||
1268 | ieee80211_rx_h_passive_scan, | 1370 | ieee80211_rx_h_passive_scan, |
1269 | ieee80211_rx_h_check, | 1371 | ieee80211_rx_h_check, |
1270 | ieee80211_rx_h_load_key, | 1372 | ieee80211_rx_h_load_key, |
@@ -1371,16 +1473,10 @@ void __ieee80211_rx(struct ieee80211_hw *hw, struct sk_buff *skb, | |||
1371 | struct ieee80211_hdr *hdr; | 1473 | struct ieee80211_hdr *hdr; |
1372 | struct ieee80211_txrx_data rx; | 1474 | struct ieee80211_txrx_data rx; |
1373 | u16 type; | 1475 | u16 type; |
1374 | int radiotap_len = 0, prepres; | 1476 | int prepres; |
1375 | struct ieee80211_sub_if_data *prev = NULL; | 1477 | struct ieee80211_sub_if_data *prev = NULL; |
1376 | struct sk_buff *skb_new; | 1478 | struct sk_buff *skb_new; |
1377 | u8 *bssid; | 1479 | u8 *bssid; |
1378 | int bogon; | ||
1379 | |||
1380 | if (status->flag & RX_FLAG_RADIOTAP) { | ||
1381 | radiotap_len = ieee80211_get_radiotap_len(skb->data); | ||
1382 | skb_pull(skb, radiotap_len); | ||
1383 | } | ||
1384 | 1480 | ||
1385 | /* | 1481 | /* |
1386 | * key references and virtual interfaces are protected using RCU | 1482 | * key references and virtual interfaces are protected using RCU |
@@ -1389,30 +1485,35 @@ void __ieee80211_rx(struct ieee80211_hw *hw, struct sk_buff *skb, | |||
1389 | */ | 1485 | */ |
1390 | rcu_read_lock(); | 1486 | rcu_read_lock(); |
1391 | 1487 | ||
1488 | /* | ||
1489 | * Frames with failed FCS/PLCP checksum are not returned, | ||
1490 | * all other frames are returned without radiotap header | ||
1491 | * if it was previously present. | ||
1492 | * Also, frames with less than 16 bytes are dropped. | ||
1493 | */ | ||
1494 | skb = ieee80211_rx_monitor(local, skb, status); | ||
1495 | if (!skb) { | ||
1496 | rcu_read_unlock(); | ||
1497 | return; | ||
1498 | } | ||
1499 | |||
1392 | hdr = (struct ieee80211_hdr *) skb->data; | 1500 | hdr = (struct ieee80211_hdr *) skb->data; |
1393 | memset(&rx, 0, sizeof(rx)); | 1501 | memset(&rx, 0, sizeof(rx)); |
1394 | rx.skb = skb; | 1502 | rx.skb = skb; |
1395 | rx.local = local; | 1503 | rx.local = local; |
1396 | 1504 | ||
1397 | rx.u.rx.status = status; | 1505 | rx.u.rx.status = status; |
1398 | rx.fc = skb->len >= 2 ? le16_to_cpu(hdr->frame_control) : 0; | 1506 | rx.fc = le16_to_cpu(hdr->frame_control); |
1399 | type = rx.fc & IEEE80211_FCTL_FTYPE; | 1507 | type = rx.fc & IEEE80211_FCTL_FTYPE; |
1400 | 1508 | ||
1401 | bogon = status->flag & (RX_FLAG_FAILED_FCS_CRC | | 1509 | if (type == IEEE80211_FTYPE_DATA || type == IEEE80211_FTYPE_MGMT) |
1402 | RX_FLAG_FAILED_PLCP_CRC); | ||
1403 | |||
1404 | if (!bogon && (type == IEEE80211_FTYPE_DATA || | ||
1405 | type == IEEE80211_FTYPE_MGMT)) | ||
1406 | local->dot11ReceivedFragmentCount++; | 1510 | local->dot11ReceivedFragmentCount++; |
1407 | 1511 | ||
1408 | if (!bogon && skb->len >= 16) { | 1512 | sta = rx.sta = sta_info_get(local, hdr->addr2); |
1409 | sta = rx.sta = sta_info_get(local, hdr->addr2); | 1513 | if (sta) { |
1410 | if (sta) { | 1514 | rx.dev = rx.sta->dev; |
1411 | rx.dev = rx.sta->dev; | 1515 | rx.sdata = IEEE80211_DEV_TO_SUB_IF(rx.dev); |
1412 | rx.sdata = IEEE80211_DEV_TO_SUB_IF(rx.dev); | 1516 | } |
1413 | } | ||
1414 | } else | ||
1415 | sta = rx.sta = NULL; | ||
1416 | 1517 | ||
1417 | if ((status->flag & RX_FLAG_MMIC_ERROR)) { | 1518 | if ((status->flag & RX_FLAG_MMIC_ERROR)) { |
1418 | ieee80211_rx_michael_mic_report(local->mdev, hdr, sta, &rx); | 1519 | ieee80211_rx_michael_mic_report(local->mdev, hdr, sta, &rx); |
@@ -1427,7 +1528,6 @@ void __ieee80211_rx(struct ieee80211_hw *hw, struct sk_buff *skb, | |||
1427 | goto end; | 1528 | goto end; |
1428 | skb = rx.skb; | 1529 | skb = rx.skb; |
1429 | 1530 | ||
1430 | skb_push(skb, radiotap_len); | ||
1431 | if (sta && !(sta->flags & (WLAN_STA_WDS | WLAN_STA_ASSOC_AP)) && | 1531 | if (sta && !(sta->flags & (WLAN_STA_WDS | WLAN_STA_ASSOC_AP)) && |
1432 | !local->iff_promiscs && !is_multicast_ether_addr(hdr->addr1)) { | 1532 | !local->iff_promiscs && !is_multicast_ether_addr(hdr->addr1)) { |
1433 | rx.flags |= IEEE80211_TXRXD_RXRA_MATCH; | 1533 | rx.flags |= IEEE80211_TXRXD_RXRA_MATCH; |
@@ -1438,14 +1538,16 @@ void __ieee80211_rx(struct ieee80211_hw *hw, struct sk_buff *skb, | |||
1438 | return; | 1538 | return; |
1439 | } | 1539 | } |
1440 | 1540 | ||
1441 | bssid = ieee80211_get_bssid(hdr, skb->len - radiotap_len); | 1541 | bssid = ieee80211_get_bssid(hdr, skb->len); |
1442 | 1542 | ||
1443 | list_for_each_entry_rcu(sdata, &local->interfaces, list) { | 1543 | list_for_each_entry_rcu(sdata, &local->interfaces, list) { |
1444 | rx.flags |= IEEE80211_TXRXD_RXRA_MATCH; | ||
1445 | |||
1446 | if (!netif_running(sdata->dev)) | 1544 | if (!netif_running(sdata->dev)) |
1447 | continue; | 1545 | continue; |
1448 | 1546 | ||
1547 | if (sdata->type == IEEE80211_IF_TYPE_MNTR) | ||
1548 | continue; | ||
1549 | |||
1550 | rx.flags |= IEEE80211_TXRXD_RXRA_MATCH; | ||
1449 | prepres = prepare_for_handlers(sdata, bssid, &rx, hdr); | 1551 | prepres = prepare_for_handlers(sdata, bssid, &rx, hdr); |
1450 | /* prepare_for_handlers can change sta */ | 1552 | /* prepare_for_handlers can change sta */ |
1451 | sta = rx.sta; | 1553 | sta = rx.sta; |