執行人: willwillhi1
整理 2022 年報告,解說 Linux 核心的 cfg80211 無線網路框架及 vwifi 運作原理,並善用 namespace 準備無線網路測試環境,預計完成以下:
以中文描述為主,應當涵蓋 vwifi 背景知識 和相關的 cfg80211 無線網路框架。
參考資訊:
struct owl_context
: 表示最基本的結構體,整個 driver 只會有一個
Dung-Ru Tsai 可以解釋一下
struct owl_context
與struct owl_vif
的關係嗎?
willwillhi1 就我的理解,owl_context 是 vwifi 用來管理所有的 virtual interface(owl_vif) 的結構體。
owl_context 裡的 vif_list 管理所有的 virtual interface, ap_list 管理所有 AP mode 的 virtual interface。
owl_context 也管理整個程式的 context,所以會有 vwifi_state 來代表目前 vwifi 的狀態。
而 owl_vif 就是一個用來表示 virtual interface 的結構體,目前 vwifi 支援的 mode 有 AP 和 STA。
struct owl_vif
: net_device 的 private data 用來表示 Virtual interface,不論是 STA 或 AP 或 Ad-hoc 都是用同一個(主要是因為裡面用 union 來區分)
另外可以從下方知道 STA mode 主要有四個 MLME 邏輯的實作:
全部實作完後這些函式的位址會被填入 cfg80211_ops
結構體中
change_virtual_intf
用來改變 virtual interface 的 type
參考 brcmf_cfg80211_change_iface
這邊只改變 virtual interface 的 iftype
當 user 做 scan 時,kernel 會執行 owl_scan
首先透過 wdev_get_owl_vif 取得 virtual interface owl_vif *vif
,然後取得 vif->lock
後,做 request 和 IFTYPE 的檢查後就把 request 填入 interface。
最後把鎖解開後就執行 schedule_work 把 scan 這項任務加入 CFS。
真正在做 scan 的函式是 owl_scan_routine
在 vwifi 裡不會真正去掃描,只會做 timeout 的檢查。
scan 時如果 timeout 就會呼叫 owl_scan_timeout
,然後就會把指定的工作加入排程,owl_scan_timeout_work
。
timeout 裡面執行 cfg80211_scan_done
,所以一定會觸發 timeout?
scan 會在 timeout 倒數結束後開始執行
inform_bss 用來通知 kernel 掃描的結果
主要是用 cfg80211_inform_bss_data
做這件事
其中的參數有 cfg80211_inform_bss data,表示頻道、頻寬、訊號
CFG80211_BSS_FTYPE_UNKNOWN
: driver doesn't know whether the data is from a beacon or probe response。
infornation element(ie): 用來放入 frame 的欄位內,表示 scan 掃描到的 AP。
第一個 byte 表示 ie 類型,第二個 byte 表示後面接著的資料長度。
cfg80211_put_bss
用來減少 cfg80211_inform_bss_data
回傳的 cfg80211_bss
結構體的 refcounter,用來決定要不要 free。
回到 owl_scan_timeout_work
最後會用 cfg80211_scan_done
通知 kernel 掃描已經結束。
由 kernel 執行 owl_connect
開始,一樣先檢查 STA 是否已連線或目前的 IFTYPE 是否為 STA。
接著把 req_ssid 改為想要連線的 SSID
這邊感覺 req_ssid 連完要改回來,因為已經是已連線的狀態了,req_ssid 比較像是正在請求連線的狀態。
然後把 owl_connect_routine
加入排程
owl_connect_routine
vif 代表自己(要求連線方)的 vitrual interface。
ap 代表被連線方的 vitrual interface。
接下來用 list_for_each_entry
走訪整個 ap_list,如果 ap->ssid
跟 vif->req_ssid
一樣就開始連線。
vwifi 實作連線的部分只用 cfg80211_connect_result
來回傳連線成功。
然後修改 vif 的各個設定:
接著是將 vif 加入 bss_list
的尾端 (AP 是 head)。
kernel 呼叫 owl_disconnect 來執行 disconnect,前面得判斷邏輯與 connect 相同,不過 disconnect 還需要加入 reason code
接下來看到 owl_disconnect_routine
在呼叫 cfg80211_disconnected
之後,driver 會進入 idle 狀態並且不會嘗試去連線其他 AP。
After it calls this function, the driver should enter an idle state and not try to connect to any AP any more.
接著更新一些參數
把這個 vif(節點) 從 bss_list
移除,表示這個節點已經沒有與這個 AP(bss_list head) 連線了。
最後把這個 vif 的 ap 清空即可
以上是 STA 的 MLME 的實作
接著看 AP 的 MLME 的實作
get_station
BIT_ULL 定義在 include/linux/bitops.h
,代表將 nr 向左位移 1 位然後轉型為 unsigned long long。
get_station 主要將資訊填入 station_info
如果有填就把 sinfo->filled
的對應的 bit 改成 1,vwifi 這邊有填入的值有
最後再依據目前是否已連線來設定 CONNECTED_TIME
start_ap
需要開始 AP mode 時由 kernel 執行 owl_start_ap。
首先印出 struct cfg80211_ap_settings *settings
各個參數。
然後 vwifi 目前 start_up 的就只是簡單的設定 AP 的 SSID 和 BSSID。
然後依照定義,將這個 AP 設為 bss_list 的 head
最後再把這個 AP 加入 ap_list
stop_ap 的操作其實與 start_ap 對應。
因為是停掉一個 AP,所以對其連線的 STA 都要從 bss_list 刪掉。
不需要其他斷開連線的操作(比如通知連線的 STA, 變更 STA 的 sme_state, etc),是因為不需要嗎
最後將這個 AP 從 ap_list 移除
不知道 owl_vif 是怎麼改的,因為初始化的時候都是固定 STA mode。
透過 hostapd
change the interface type
先配置整個 driver 唯一的 context,然後初始化裡面的參數。
接下來依照 station 數量來建立虛擬網路裝置
首先要先建立 nl80211 上用來描述一個的裝置結構體 wiphy,通過 owl_cfg80211_add 來建立。
註解的這部分 which will call cfg80211_ops->add_iface()
,我去查 cfg80211_ops 好像沒有這個成員。
該註解已過時,請提交 pull request 修正
jservImage Not Showing Possible ReasonsLearn More →
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
建立 wiphy 的函式可以用 wiphy_new_nm 或 wiphy_new,差在要不要自己取名。
設定這個裝置支援的 interface type (AP、STA)。
接著填入這個裝置的支援 band 以及可以 scan 的最大數量。
設定 signal strength value
定義在 include/net/cfg80211.h
最後在 cfg80211 註冊這個 wiphy
owl_interface_add
接下來要替這個裝置建立一個虛擬介面 (virtual interface)。
首先先 allocate 一個網路裝置
第一個參數 sizeof(struct owl_vif) 代表 private data 的空間大小,這邊 private data 是指向該裝置的 virtual interface。
然後是基本的網路裝置的初始化函式 ether_setup。
接下來就是填入 vitrual interface 的資料,
其中有個結構體 wireless_dev 用來表示裝置的無線介面,要把 net_device 的 ieee80211_ptr 指向 wireless_dev 結構體。
the network interface’s ieee80211_ptr pointer to a struct wireless_dev which further describes the wireless part of the interface, normally this struct is embedded in the network interface’s private data area.
值得一提的地方是設定 MAC address 地方,用的方法是直接將裝置名稱複製過去,不過要注意的是要避免使用到 multicast 的 MAC address,也就是說整個 MAC address 最高位的 byte 的最低位 bit 不能是 1,所以這邊就直接設定成 0。
這邊的 snprintf 的 size 部分,我覺得應該可以是 ETH_ALEN - 1,因為 intf_name[ETH_ALEN] 已經填 0 了。
提交 pull request
jservImage Not Showing Possible ReasonsLearn More →
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
最後註冊這個 net_device
即可:
以上可以參考 brcmf_cfg80211_request_sta_if
接著繼續 vif 的初始化
初始化 scan_timeout 計時器
Initialize all of a work item
最後把該 virtual interface 加入 owl 的 vif->list
This structure defines the management hooks for network devices.
The following hooks can be defined; unless noted otherwise, they are optional and can be filled with a null pointer.
net_device_ops 相當於 network device 的管理函式的集合,除非有定義是必要,不然都是可有可無。
參考 Network Drivers 有講最基本的概念
實作的部分參考 Linux Kernel(16.1)- Network Device Driver, simple snull
This function is called when a network device transitions to the up state.
owl_ndo_open 的定義如下
netif_start_queue 用來表示這個裝置允許傳輸
This function is called when a network device transitions to the down state.
這裡做的事比較單純,因為裝置停止傳輸了,所以也要把收到的封包刪除。
最後再停止接收封包
Whenever an application needs to get statistics for the interface, this method is called. This happens, for example, when ifconfig or netstat -i is run.
可以取得 device 的統計資料,通常透過 ifconfig 或 netstat -i 呼叫。
Method that initiates the transmission of a packet. The full packet (protocol headers and all) is contained in a socket buffer (sk_buff) structure.
開始傳送封包,整個封包由 sk_buff 結構體表示。
輸入的參數為 sk_buff *skb
和 net_device *dev
分別代表要傳送的封包和負責傳送的裝置。
首先先取得傳送跟接收的 virtual interface 和 第二層的標頭
接下來會判斷自己是 STA 或 AP,各自會有不同的實作。
這邊的邏輯很簡單,STA 傳輸的對象只可能是目前連線中的 AP(multicast/broadcast 都會先傳給 AP 轉傳),所以就直接傳。
broadcast:
unicast:
最後就是判斷封包是否 drop,以及最後要把封包 free 掉。
__owl_ndo_start_xmit
這個函式負責把封包的資料取出
一開始先配置 owl_packet 所需空間
然後從 sk_buff 取資料填入 owl_packet
接下來把封包放入目的端的 virtual interface 的 rx_queue
然後是在來源端的 virtual interface 填入相關資料
最後還要處理接收封包
owl_rx
處理 rx_queue 收到的封包,將它轉成 skb_buff 的格式。
先處理第一個收到封包
接下來可以先看到這篇關於 On the alignment of IP packets 的說明,可以了解因為 Ethernet Header(14B),導致後面的 IP Header(16B) 無法對齊 four-byte boundary,所以才要多了前面的 2B。
skb_reserve 用來移動 data、tail 指標。
skb_put 往下移動 tail 指標用來增加 data 的空間,並回傳移動前的 tail 指標位置。
最後將該封包從 rx_queue 移除並釋放空間。
AP 處理 multicast/broadcast packet 的部分,會轉傳給除了來源 STA 外的所有 STA。
因為 broadcast 是 multicast 的一個特例,所以可以用 is_multicast_ether_addr 來判斷,若是則將 skb 複製到 skb1。
如果 AP 收到的封包為 unicast,會有兩種可能(給 AP 的、給 BSS 內的某 STA),這邊先處理第二種可能,一樣先把 skb 複製到 skb1,然後把 skb assign 為 NULL。
接著先處理轉傳的部分(送給其他 STA)
處理第一種可能,這個封包就是給自己(STA 或 AP)的就送給 protocol stack 處理
TODO
檢查目前是否已經載入指定的模組,如果有就回傳 0,沒有就回傳 1。
載入指定模組。
先把 xxx.ko 去除 .ko,xxx 存到 noko_name
檢查這個模組是否已經載入,如果是則把它移除
最後就是載入模組,並回傳 check_kmod 的結果
也是用來載入模組,不過與 insert_kmod 不同的地方在於它會檢查模組的相依性,並載入所需的模組。
移除模組
用 check_kmod 檢查是否已經載入,若為否就直接退出回傳 0
接著就移除模組
在 vwifi 中我沒有找到執行 start_hostapd 的地方
由 hostapd 執行
主要就是找到 hostspd 然後執行
停止 hostapd 這個行程
設定 ROOT 為移動到當前目錄的上一個資料夾然後印出該路徑。
接著引用 common.sh 裡面的函式
final_ret 代表最後一次執行的結果,0 代表正確,其他數字代表哪個地方出錯,方便 debug。
先載入 cfg80211 模組
載入 vwifi 模組
檢查 hostapd 有沒有安裝
如果前面都正常執行就可以開始測試
iw dev 的說明
List all network interfaces for wireless hardware.
會印出
接著取出其裝置名稱,把 #
去掉
以上面的輸出為例,owl0_phy 會是 phy#4
然後經過 ${owl0_phy/\#/}
會是 phy4
建立虛擬網路環境分別叫做 ns0, ns1, ns2
接著把三個虛擬裝置加入到對應的 namespace
啟用各個 namespace 的 lo, owl 網路裝置,並指定 owl0 為 AP,執行 hostapd
這邊執行後回傳的結果為
不知道是不是正常
TODO:
不是很懂 hostapd 的運作,之後要詳細探討其流程
給虛擬網路裝置(owl0, owl1, owl2) 定義 IP 位址
設定都做好後就開始測試了
第一個測試是 owl1 ping owl2,因為未連線所以應該要失敗
接著做 scan (sudo ip netns exec ns1 iw dev owl1 scan
)
iw dev <devname> scan
:
Scan on the given frequencies and probe for the given SSIDs(or wildcard if not given) unless passive scanning is requested.
會得到以下輸出(掃描結果)
做 connect 後進行 link 命令
iw dev <devname> link
:
Print information about the current link, if any.
會得到
接著會比較兩個命令得到的 MAC address 有沒有相同,確保是連到同一個 AP
owl2 也要做相同的事(scan, connect, link)
connect 完後就是要測試 STA 之間的連線,也就是 owl1 ping owl2,會得到以下結果
在讀 verify.sh 時,發現 160 行 ~ 164 行發現錯字
修正於 commit dbfd05e
這邊因為不清楚 hostapd 的流程,所以去觀察他的執行過程。
以下通過 trave-cmd 來追蹤 hostapd 的執行,然後用 80211 的關鍵字搜尋。
執行完後用 trace-cmd report
輸出追蹤過程
以下僅列出要觀察的部份
首先是 change interface 的部份 (STA -> AP)
去觀察 cfg80211_change_iface 的程式
可以發現會檢查 driver 有沒有設定 change_virtual_intf
函式
然後針對不同的 interface 做不同的前製處理,因為 vwifi 只會從 STA -> AP,所以這邊就只看 STA 的部份,可以發現它會先去做 disconnect 的操作。
接下來是真正做切換的地方
rdev->ops->change_virtual_intf
這邊的呼叫就是在執行 vwifi 中設定好的函式,vwifi 在這邊只是簡單的切換 iftype 而已。
回到 trace-cmd report 中尋找關於啟動 AP 的部份
再去查看 nl80211_start_ap 的程式
如果沒有設定 start_ap 函式就會跳錯
觀察 params
最後看到真正做 start_ap 的地方,做完之後也要更新 wdev 的值
可以發現就是在執行rdev->ops->start_ap(&rdev->wiphy, dev, settings)
,也就是我們在 vwifi 寫好的 start_up 函式。
比照 Testing a Linux Routing Daemon in a Simulated Environment,準備多個 SSID/STA 的網路測試環境,並改進現有 vwifi 的 CI 流程
原本的程式為
但是我發現如果執行到最後 final_ret 不為 0,就不會執行到 stop_hostapd ~ rm 的命令。
導致如果重新 make check 之前,就需要手動把這些命令打一遍。
所以我就把這些命令從 if 移出,變成
這麼一來就不用每次都要重新打一遍一長串命令。
TODO: 提交 pull request
jservImage Not Showing Possible ReasonsLearn More →
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
首先因為是兩個 AP,不能共用同一個 hostapd.conf
所以我分成兩個,hostapd1.conf 和 hostapd2.conf
總共要新增六個虛擬裝置(2 個 AP 和 4 個 STA)
建立六個 network namespace
將虛擬裝置加入各個 network namespace
owl0 和 owl3 用 hostapd 使其成為 AP
然後各自啟用虛擬網路裝置
新加入的網路裝置
owl3 分配 ip 為 10.0.0.4/24
owl4 分配 ip 為 10.0.0.5/24
owl5 分配 ip 為 10.0.0.6/24
之後做的測試與之前相同,這邊就不列出來了。
commit e3d3dc8
commit f2162a2
想要用 iw station dump 指令列出該 AP 所管理的 STA 時,發現會什麼都印不出來。
所以便去看 iw station dump 都做了些什麼
用 trace-cmd 去追蹤,看到 iw 的部份
特別去看 nl80211_dump_station 的程式碼,然後觀察以下程式的行為。
這邊會先判斷有沒有定義 dump_station 函式
然後進入無限迴圈,sta_idx 會從 0 開始,然後逐一累加直到,得到的 err 為 -ENOENT (全部掃描完)或函式執行有誤(err 不為 0 或 -ENOENT)
如果執行正確,sinfo 就會被填入所求的 STA 資訊,然後透過 nl80211_send_station 函式送給上層的 nl80211。
迴圈最後再把 sta_iidx 累加 1,繼續執行。
之後去觀摩 mac80211 是怎麼實作 station_dump 的
mac80211,從以下可以知道 mac80211 實作 station dump 是叫做 ieee80211_dump_station。
去找 mac80211 的 ieee80211_dump_station
這邊的 IEEE80211_DEV_TO_SUB_IF 的概念就是去找出 dev 的 priv_data(也就是 interface 的結構體)。
sta_info_get_by_idx 會回傳找到的 STA,如果有找到 sta (sta 不為 NULL),先把 ret 改為 0,代表有找到,然後把 sta 資訊填入 sinfo 即可。
最後 ret 回傳的值可以告訴 nl80211_dump_station 要不要繼續找下一個 sta。
然後去參考 sta_info_get_by_idx 的實作
可以發現整個邏輯就是用 list_for_each_entry_rcu 去走訪整個雙向鍊結串列,然後以 idx 為中止條件,
如果全部走完就回傳 NULL,代表已經全部傳完了。
開始對 vwifi 修改
先新增 station_dump 的對應的函式
新增 owl_dump_station 函式,參考 mac80211 的實作
先用 dev 找出 AP 的 owl_vif
然後印出 idx 觀察程式有沒有如預期執行
初始化需要用到參數
然後是用迴圈走到第 idx 個 STA
依據 sta_vif 來修改 ret 的值
接著把 sta->bssid 複製到 mac 參數
最後就是參考 vwifi 原本的 owl_get_station 來填入 sinfo 的值,但要注意最後要改為回傳 ret。
執行時可以用 pr_info 來確定程式執行有如預期
最後的執行結果(以 owl0 為 AP,owl1 和 owl2 為連線的 STA)
可以正確的讓 vwifi 正確回傳該 AP 目前連線的 STA 了。
提交 pull request
jservImage Not Showing Possible ReasonsLearn More →
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
a STA listens on the Beacon frames that an AP periodically sends in each channel to obtain AP information. A Beacon frame contains information including the SSID and supported rate.
To save power of a STA, enable the STA to passively scan wireless networks.
passive scan 簡單來說就是 AP 會以固定週期傳送 Beacon Frame,而 STA 被動的接收到 Beacon Frame 後可以藉此得知該 AP 的存在。
目前 vwifi 只有實作 active scan,如果想要作到 roam 的效果,考慮到一般的 roam 的條件:
(1) A weak signal quality: The AP’s current RSSI value is weak (below -75 dBm)
(2) Loss of beacons: When beacons from a connected AP are not received after 2 seconds with an active station
(3) Channel utilization (CU): Multiple clients are connected to the same AP. Despite having a strong radio signal, connectivity may be disrupted due to network overload. When this is the case, the AP will notify clients of its current traffic using the CU factor in the beacons. The mobile device will start scanning if the received CU value is greater than 70% and the current RSSI value is between -65 and -75 dBm.
由以上可知必須要有接收 beacon 的功能
所以我就先實作了 beacon 的功能
模擬 beacon 的行為,需要考慮到的地方是 AP 會需要定時的發出 beacon 給 STA。
因此我利用 delayed_work 來實作
先在 owl_vif 的 AP mode 的部份加入 dw_send_beacon
接著是觸發的條件,因為 vwifi 在建立一個 virtual interface 時預設都是 STA mode,所以是不需要進行發送 beacon 的動作的,需要進行的時間點是從 STA mode 變為 AP mode 時,也就是執行 owl_start_ap 時,所以我在 owl_start_ap 多加入了:
刪除原本在 STA mode 中的排程工作
初始化 delay work,以及將對應的工作加入排程,並且預設延遲 10 秒,這個延遲時間代表 beacon interval。
之後考量到一般 real world 的 beacon interval 都是設定為 100 ms,所以我把它改為 0.1 * HZ
。
接下來看到 dw_send_beacon 結構體所設定的 func: owl_send_beacon_work 做了什麼事
簡單來說就是執行 beaconing 函式,以及再這之後繼續排定下一個發出 beacon 的任務。
然後看到 beaconing 函式
這邊大部分的程式都是參考 inform_bss,因為兩者要做的事本質上是不變的,差別只是在於 active 和 passive。
beaconing 函式的 input 為需要發出 beacon 的 AP,接著我們就利用之前建立的雙向鍊結佇列 owl->vif_list 來走訪所有的 STA mode 的 interface。
我們會對走訪到的 STA 發出 beacon,但是這邊有一個重點部份還未實作,也就是未來可以依據模擬的空間來填入訊號強度,目前就只是單純的對所有 STA 發出 beacon。
假如我們可以作到依據模擬空間算出訊號強度,那我們就可以讓 vwifi 做到模擬 beacon loss 的效果,最終實作 roam。
最後是再移除 vwifi 時運行的 owl_stop_ap 的修改,要取消已經加入排程的 delay work。
最後用 dmesg 印出執行中的訊息,可以發現 vwifi 確實可以成功的每隔 10 秒就進行一次發出 beacon 的動作。
下面的執行的情境是有一個 AP(owl0) 和兩個 STA(owl1 & owl2)。
接下來測試另外一個情境,兩個 AP(owl0 & owl1) 和一個 STA(owl2)。
在 owl_get_station
函式中,應當模擬 rate 和 mcs (Modulation Coding Scheme),參照 mac80211_hwsim
撰寫 vwifi 運作原理的英文描述,日後可整合進 The Linux Kernel Module Programming Guide