最近需要对OpenWRT内核中的beacon帧做修改,在网上找了很久也没找到相关的帖子,现在自己把修改的方法给弄明白了,写出来供大家参考一下,里面有错误的地方希望诸位大神能给我指出来,小白先在此谢过了。
要修改beacon帧,就需要了解帧的写入和发送的过程,今天我们就来研究一下beacon帧的写入和发送过程。
beacon帧发送机制:
beacon帧的发送是通过tasklet机制实现的,tasklet是软中断实现的下半部处理机制,用于中断处理流程的下半部。核心函数是beacon.c中的ath9k_beacon_tasklet函数,(将该函数的指针传递给tasklet_init()即可实现tasklet_struct的动态创建,当tasklet被调度以后,ath9k_beacon_tasklet函数会被执行)。函数体如下所示:
1 void ath9k_beacon_tasklet(unsigned long data) 2 { 3 struct ath_softc *sc = (struct ath_softc *)data; 4 struct ath_hw *ah = sc->sc_ah; 5 struct ath_common *common = ath9k_hw_common(ah); 6 struct ath_buf *bf = NULL; 7 ... 8 9 bf = ath9k_beacon_generate(sc->hw, vif); 10 11 ... 12 if (bf) { 13 ath9k_reset_beacon_status(sc); 14 15 16 ath_dbg(common, BEACON, "Transmitting beacon for slot: %d\n", slot); 17 18 19 /* NB: cabq traffic should already be queued and primed */ 20 ath9k_hw_puttxbuf(ah, sc->beacon.beaconq, bf->bf_daddr); 21 22 if (!edma) 23 { 24 ath9k_hw_txstart(ah, sc->beacon.beaconq); 25 } 26 } 27 }
struct ath_softc中的struct ath_beacon_config cur_beacon_conf成员中定义了名为beacon_interval的int型变量,每隔一个beacon_interval(即beacon帧的帧间隔),系统就尝试发送一个beacon帧,此时开启中断,tasklet就被调度,执行ath9k_beacon_tasklet函数,该函数就会调用ath9k_beacon_generate生成beacon帧,然后调用ath9k_hw_puttxbuf将beacon帧放入发送缓存队列中,接着调用ath9k_hw_txstart将beacon帧发送出去。
研究收发机制是为了修改beacon帧,因此接下来我们看一看beacon帧是如何产生的。这就要研究刚才提到的ath9k_beacon_generate函数了。ath9k_beacon_generate函数体如下所示:
1 static struct ath_buf *ath9k_beacon_generate(struct ieee80211_hw *hw, struct ieee80211_vif *vif) 2 { 3 struct ath_softc *sc = hw->priv; 4 struct ath_common *common = ath9k_hw_common(sc->sc_ah); 5 struct ath_buf *bf; 6 struct ath_vif *avp = (void *)vif->drv_priv; 7 struct sk_buff *skb; 8 struct ath_txq *cabq = sc->beacon.cabq; 9 struct ieee80211_tx_info *info; 10 struct ieee80211_mgmt *mgmt_hdr; 11 int cabq_depth; 12 13 14 if (avp->av_bcbuf == NULL) 15 return NULL; 16 17 18 bf = avp->av_bcbuf; 19 skb = bf->bf_mpdu; 20 if (skb) { 21 dma_unmap_single(sc->dev, bf->bf_buf_addr, skb->len, DMA_TO_DEVICE); 22 dev_kfree_skb_any(skb); 23 bf->bf_buf_addr = 0; 24 bf->bf_mpdu = NULL; /*清空缓存*/ 25 } 26 27 28 29 skb = ieee80211_beacon_get(hw, vif); 30 31 32 if (skb == NULL) 33 return NULL; /*skb生成失败退出*/ 34 35 bf->bf_mpdu = skb; /*将生成的beacon帧缓存赋给bf结构体,此处是指针赋值,可以只用任一指针对对象进行修改*/ 36 37 38 39 40 41 mgmt_hdr = (struct ieee80211_mgmt *)skb->data; 42 mgmt_hdr->u.beacon.timestamp = avp->tsf_adjust; /*用ieee80211_mgmt结构体将skb->data中的前面若干个字段提取出来(包括frame_control,duration,da,sa,bssid,seqctrl,以及beacon帧特有的timestamp,beacon interval,capability information,variable字段),并对其timestamp字段进行数据的写入*/ 43 44 45 ... 46 47 return bf; 48 }
在ath9k_beacon_generate中,生成beacon帧的主要函数是ieee80211_beacon_get,该函数调用ieee80211_beacon_get_tim进行beacon帧的生成,返回一个struct sk_buff型的结构体。
插播一个概念——对sk_buff结构体做一个介绍:sk_buff是Linux网络代码中最重要的结构体之一。它是Linux在其协议栈里传送的结构体,也就是所谓的“包”,在他里面包含了各层协议的头部,比如ethernet, ip ,tcp ,udp等等。也有相关的操作等。熟悉他是进一步了解Linux网络协议栈的基础。该结构体可以看作一个指针的集合,里面存储了指向数据区域的指针(char *data指针就指向了待发送的beacon的信息)。另外还包含了一些指标性的变量(比如头部长度、数据长度、缓冲区大小等)。下面可以看到beacon数据就是写入到该结构体中去的,在后面的接收部分可以看到数据的读取也是通过sk_buff完成的。
插播结束,下面我们再来看一看ieee80211_beacon_get_tim的函数体,看看是如何生成beacon帧的:
1 struct sk_buff *ieee80211_beacon_get_tim(struct ieee80211_hw *hw, struct ieee80211_vif *vif, u16 *tim_offset, u16 *tim_length) 2 { 3 struct ieee80211_local *local = hw_to_local(hw); 4 struct sk_buff *skb = NULL; 5 struct ieee80211_tx_info *info; 6 struct ieee80211_sub_if_data *sdata = NULL; 7 enum ieee80211_band band; 8 struct ieee80211_tx_rate_control txrc; 9 struct ieee80211_chanctx_conf *chanctx_conf; 10 11 12 rcu_read_lock(); 13 14 15 sdata = vif_to_sdata(vif); /*从虚拟接口中读取信息*/ 16 chanctx_conf = rcu_dereference(sdata->vif.chanctx_conf); 17 18 19 ... 20 21 22 if (sdata->vif.type == NL80211_IFTYPE_AP) { /*对虚拟接口类型进行判断,对于AP/ADHOC/MESH,要采取不同的beacon生成方式*/ 23 struct ieee80211_if_ap *ap = &sdata->u.ap; 24 struct beacon_data *beacon = rcu_dereference(ap->beacon); /*根据从虚拟接口中读取的信息,生成beacon_data的基本数据结构*/ 25 26 27 if (beacon) { 28 /* 29 * headroom, head length, 30 * tail length and maximum TIM length 31 */ 32 skb = dev_alloc_skb(local->tx_headroom + beacon->head_len + beacon->tail_len + 256);/*分配内存,分配缓冲区和一个sk_buff结构*/ 33 if (!skb) 34 goto out; 35 36 37 skb_reserve(skb, local->tx_headroom); 38 memcpy(skb_put(skb, beacon->head_len), beacon->head, beacon->head_len); /*根据beacon_data结构,将头部的信息(TIM之前的)写入beacon帧,部分字段先预留等待后续程序的写入*/ 39 40 41 ieee80211_beacon_add_tim(sdata, &ap->ps, skb); /*将TIM信息写入sk_buffer中*/ 42 43 44 if (tim_offset) 45 *tim_offset = beacon->head_len; 46 if (tim_length) 47 *tim_length = skb->len - beacon->head_len; 48 49 50 if (beacon->tail) 51 memcpy(skb_put(skb, beacon->tail_len), 52 beacon->tail, beacon->tail_len); 53 } else 54 goto out; 55 } else if (sdata->vif.type == NL80211_IFTYPE_ADHOC) { 56 57 ... 58 59 } else if (ieee80211_vif_is_mesh(&sdata->vif)) { 60 61 ... 62 63 64 } else { 65 WARN_ON(1); 66 goto out; 67 } 68 69 70 ... 71 72 73 out: 74 rcu_read_unlock(); 75 return skb; 76 } 77 EXPORT_SYMBOL(ieee80211_beacon_get_tim);
程序的执行过程在注释中已经写出来了。beacon帧的帧结构分为几个部分,MAC头部分、强制字段部分和可选字段部分,可选字段不一定会用到,只有需要用到时才会出现,可选字段中包括了若干保留字段,如果要对帧结构字段作增加,则需要利用这部分保留字段(每个字段至少要包含ELEMENT ID、字段长度和字段内容三部分的信息)。MAC头部信息和强制字段是通过memcpy函数写入的,在memcpy函数对beacon的写入过程中,所需信息是从虚拟接口获得的,在写入的时候采用直接复制的方法,将MAC头部信息和强制字段(Mandatory)写入sk_buff。为了研究选择性字段(Optional)如何写入,我们接下来需要对TIM的添加方法进行一下梳理,看一看ieee80211_beacon_add_tim函数的执行过程——ieee80211_beacon_add_tim在自旋锁上锁的条件下调用__ieee80211_beacon_add_tim函数,如果自旋锁处在解锁状态则对自旋锁上锁然后调用__ieee80211_beacon_add_tim函数,下面研究一下__ieee80211_beacon_add_tim函数:
1 static void __ieee80211_beacon_add_tim(struct ieee80211_sub_if_data *sdata, 2 struct ps_data *ps, struct sk_buff *skb) 3 { 4 u8 *pos, *tim; /*pos是一个重要的指针,下面将会看到程序通过指针偏移的方法对beacon帧进行字段的写入*/ 5 int aid0 = 0; 6 int i, have_bits = 0, n1, n2; 7 8 9 ... 10 11 12 tim = pos = (u8 *) skb_put(skb, 6); /*skb_put是sk_buff的管理函数,的用途是获取当前sk_buff的尾部指针位置,同时根据当前要写入的数据的总长度对sk_buff的大小进行调整*/ 13 *pos++ = WLAN_EID_TIM; /*这里就是TIM的写入过程,通过指针偏移,分别以字节为单位写入ELEMENT ID,字段长度值,字段内容值*/ 14 *pos++ = 4; 15 *pos++ = ps->dtim_count; 16 *pos++ = sdata->vif.bss_conf.dtim_period; 17 18 19 if (ps->dtim_count == 0 && !skb_queue_empty(&ps->bc_buf)) 20 aid0 = 1; 21 22 23 ps->dtim_bc_mc = aid0 == 1; 24 25 26 if (have_bits) { 27 /* Find largest even number N1 so that bits numbered 1 through 28 * (N1 x 8) - 1 in the bitmap are 0 and number N2 so that bits 29 * (N2 + 1) x 8 through 2007 are 0. */ 30 n1 = 0; 31 for (i = 0; i < IEEE80211_MAX_TIM_LEN; i++) { 32 if (ps->tim[i]) { 33 n1 = i & 0xfe; 34 break; 35 } 36 } 37 n2 = n1; 38 for (i = IEEE80211_MAX_TIM_LEN - 1; i >= n1; i--) { 39 if (ps->tim[i]) { 40 n2 = i; 41 break; 42 } 43 } 44 45 46 /* Bitmap control */ 47 *pos++ = n1 | aid0; 48 /* Part Virt Bitmap */ 49 skb_put(skb, n2 - n1); 50 memcpy(pos, ps->tim + n1, n2 - n1 + 1); 51 52 53 tim[1] = n2 - n1 + 4; 54 } else { 55 *pos++ = aid0; /* Bitmap control */ 56 *pos++ = 0; /* Part Virt Bitmap */ 57 } 58 59 60 }
经过这样一个流程以后,beacon帧的信息就被写入并发送出去了。可以看出,在ath9k_beacon_generate函数中,程序会按照帧结构的字段次序在相应的位置写入beacon帧的帧信息。这给我们下一步对帧字段的添加、帧结构的修改提供了参考。【未完待续】