--- tags: vwifi --- # vwifi 程式碼閱讀 struct owl_context: 表示最基本的結構體,整個 driver 只會有一個 ``` struct owl_context { /* We may not need this lock, cause vif_list would not change during * the whole lifetime. */ struct mutex lock; /* Indicate the program state */ enum vwifi_state state; /* List for maintaining all interfaces */ struct list_head vif_list; /* List for maintaining multiple AP */ struct list_head ap_list; }; ``` struct owl_vif: net_device 的 private data 用來表示 Virtual interface,不論是 STA 或 AP 或 Ad-hoc 都是用同一個(主要是因為裡面用 union 來區分) 另外可以從下方知道 STA mode 主要有四個 MLME 邏輯的實作: ``` struct work_struct ws_connect, ws_disconnect; struct work_struct ws_scan, ws_scan_timeout; ``` 全部實作完後這些函式的位址會被填入 cfg80211_ops 結構體中 ``` static struct cfg80211_ops owl_cfg_ops = { .change_virtual_intf = owl_change_iface, .scan = owl_scan, .connect = owl_connect, .disconnect = owl_disconnect, .get_station = owl_get_station, .start_ap = owl_start_ap, .stop_ap = owl_stop_ap, }; ``` ### MLME 函式實作 #### change_virtual_intf 用來改變 virtual interface 的 type 參考 [brcmf_cfg80211_change_iface](https://elixir.bootlin.com/linux/latest/source/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c#L1199) 這邊就只是單純的改變 virtual interface 的 iftype ``` switch (type) { case NL80211_IFTYPE_STATION: case NL80211_IFTYPE_AP: ndev->ieee80211_ptr->iftype = type; break; default: pr_info("owl: invalid interface type %u\n", type); return -EINVAL; } ``` #### scan 當 user 做 scan 時,kernel 會執行 owl_scan 首先透過 wdev_get_owl_vif 取得 virtual interface `owl_vif *vif`,然後取得 vif->lock 鎖後,做 request 和 IFTYPE 的檢查後就把 request 填入 interface。 ``` vif->scan_request = request ``` 最後把鎖解開後就執行 schedule_work 把 scan 這項任務加入 CFS。 真正在做 scan 的函式是 owl_scan_routine 在 vwifi 裡並不會真正去掃描,只會做 timeout 的檢查。 ``` mod_timer(&vif->scan_timeout, jiffies + msecs_to_jiffies(SCAN_TIMEOUT_MS)); ``` #### timeout scan 時如果 timeout 就會呼叫 owl_scan_timeout,然後就會把 timeout 加入排程,owl_scan_timeout_work。 :::info timeout 裡面執行 cfg80211_scan_done,所以一定會觸發 timeout? ::: inform_bss 用來通知 kernel 掃描的結果 主要是用 cfg80211_inform_bss_data 做這件事 其中的參數有 cfg80211_inform_bss data,表示頻道、頻寬、訊號 ``` struct cfg80211_inform_bss data = { /* the only channel */ .chan = &ap->wdev.wiphy->bands[NL80211_BAND_2GHZ]->channels[0], .scan_width = NL80211_BSS_CHAN_WIDTH_20, .signal = DBM_TO_MBM(rand_int_smooth(-100, -30, jiffies)), }; ``` CFG80211_BSS_FTYPE_UNKNOWN: driver doesn't know whether the data is from a beacon or probe response。 bssid 表示 MAC address,共 48 bits。 ``` char bssid[6] = {0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}; ``` infornation element(ie): 用來放入 frame 的欄位內,表示 scan 掃描到的 AP。 第一個 byte 表示 ie 類型,第二個 byte 表示後面接著的資料長度。 ``` u8 *ie = kmalloc(ap->ssid_len + 2, GFP_KERNEL); ie[0] = WLAN_EID_SSID; ie[1] = ap->ssid_len; memcpy(ie + 2, ap->ssid, ap->ssid_len); ``` cfg80211_put_bss 用來減少 cfg80211_inform_bss_data 回傳的 cfg80211_bss 結構體的 refcounter,用來決定要不要 free。 回到 owl_scan_timeout_work 最後會用 cfg80211_scan_done 通知 kernel 掃描已經結束。 ``` if (mutex_lock_interruptible(&vif->lock)) return; /* finish scan */ cfg80211_scan_done(vif->scan_request, &info); vif->scan_request = NULL; mutex_unlock(&vif->lock); ``` #### connect 由 kernel 執行 owl_connect 開始,一樣先檢查 STA 是否已連線或目前的 IFTYPE 是否為 STA。 接著把 req_ssid 改為想要連線的 SSID ``` vif->sme_state = SME_CONNECTING; vif->ssid_len = sme->ssid_len; memcpy(vif->req_ssid, sme->ssid, sme->ssid_len); ``` :::info 這邊感覺 req_ssid 連完要改回來,因為已經是已連線的狀態了,req_ssid 比較像是正在請求連線的狀態。 ::: 然後把 owl_connect_routine 加入排程 owl_connect_routine vif 代表自己(要求連線方)的 vitrual interface。 ap 代表被連線方的 vitrual interface。 ``` struct owl_vif *vif = container_of(w, struct owl_vif, ws_connect); struct owl_vif *ap = NULL; ``` 接下來用 list_for_each_entry 走訪整個 ap_list,如果 ap->ssid 跟 vif->req_ssid 一樣就開始連線。 vwifi 實作連線的部分只用 cfg80211_connect_result 來回傳連線成功。 然後修改 vif 的各個設定: ``` memcpy(vif->ssid, ap->ssid, ap->ssid_len); memcpy(vif->bssid, ap->bssid, ETH_ALEN); vif->sme_state = SME_CONNECTED; vif->conn_time = jiffies; vif->ap = ap; ``` 接著是將 vif 加入 bss_list 的最後(AP 是 head)。 ``` list_add_tail(&vif->bss_list, &ap->bss_list); ``` #### disconnect kernel 呼叫 owl_disconnect 來執行 disconnect,前面得判斷邏輯與 connect 相同,不過 disconnect 還需要加入 reason code ``` vif->disconnect_reason_code = 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. ``` cfg80211_disconnected(vif->ndev, vif->disconnect_reason_code, NULL, 0, true, GFP_KERNEL); ``` 接著更新一些參數 ``` vif->disconnect_reason_code = 0; vif->sme_state = SME_DISCONNECTED; ``` 把這個 vif(節點) 從 bss_list 移除,表示這個節點已經沒有與這個 AP(bss_list head) 連線了。 ``` list_del(&vif->bss_list); ``` 最後把這個 vif 的 ap 清空即可 ``` vif->ap = NULL; ``` 以上是 STA 的 MLME 的實作 接著看 AP 的 MLME 的實作 #### get_station BIT_ULL 定義在 `/include/linux/bitops.h`,代表將 nr 向左位移 1 位然後轉型為 unsigned long long。 ``` #define BIT_ULL(nr) (1ULL << (nr)) ``` get_station 主要在做的事就是將資訊填入 station_info ``` struct station_info *sinfo ``` 如果有填就把 sinfo->filled 的對應的 bit 改成 1,vwifi 這邊有填入的值有 ``` sinfo->filled = BIT_ULL(NL80211_STA_INFO_TX_PACKETS) | BIT_ULL(NL80211_STA_INFO_RX_PACKETS) | BIT_ULL(NL80211_STA_INFO_TX_FAILED) | BIT_ULL(NL80211_STA_INFO_TX_BYTES) | BIT_ULL(NL80211_STA_INFO_RX_BYTES) | BIT_ULL(NL80211_STA_INFO_SIGNAL) | BIT_ULL(NL80211_STA_INFO_INACTIVE_TIME); ``` 最後再依據目前是否已連線來設定 CONNECTED_TIME ``` if (vif->sme_state == SME_CONNECTED) { sinfo->filled |= BIT_ULL(NL80211_STA_INFO_CONNECTED_TIME); sinfo->connected_time = jiffies_to_msecs(jiffies - vif->conn_time) / 1000; } ``` #### start_ap 需要開始 AP mode 時由 kernel 執行 owl_start_ap。 首先印出 `struct cfg80211_ap_settings *settings` 各個參數。 ``` pr_info("owl: %s start acting in AP mode.\n", ndev->name); pr_info("ctrlchn=%d, center=%d, bw=%d, beacon_interval=%d, dtim_period=%d,", settings->chandef.chan->hw_value, settings->chandef.center_freq1, settings->chandef.width, settings->beacon_interval, settings->dtim_period); pr_info("ssid=%s(%zu), auth_type=%d, inactivity_timeout=%d", settings->ssid, settings->ssid_len, settings->auth_type, settings->inactivity_timeout); ``` 然後 vwifi 目前 start_up 的就只是簡單的設定 AP 的 SSID 和 BSSID。 ``` vif->ssid_len = settings->ssid_len; memcpy(vif->ssid, settings->ssid, settings->ssid_len); memcpy(vif->bssid, vif->ndev->dev_addr, ETH_ALEN); ``` 然後依照定義,將這個 AP 設為 bss_list 的 head ``` /* AP is the head of vif->bss_list */ INIT_LIST_HEAD(&vif->bss_list); ``` 最後再把這個 AP 加入 ap_list ``` /* Add AP to global ap_list */ list_add_tail(&vif->ap_list, &owl->ap_list); ``` #### stop_ap stop_ap 的操作其實與 start_ap 對應。 因為是停掉一個 AP,所以對其連線的 STA 都要從 bss_list 刪掉。 :::info 不需要其他斷開連線的操作(比如通知連線的 STA, 變更 STA 的 sme_state, etc),是因為不需要嗎 ::: ``` list_for_each_entry_safe (pos, safe, &vif->bss_list, bss_list) list_del(&pos->bss_list); ``` 最後將這個 AP 從 ap_list 移除 ``` list_del(&vif->ap_list); ``` #### change_virtual_intf :::info 不知道 owl_vif 是怎麼改的,因為初始化的時候都是固定 STA mode ::: change the interface type ``` static int owl_change_iface(struct wiphy *wiphy, struct net_device *ndev, enum nl80211_iftype type, struct vif_params *params) { switch (type) { case NL80211_IFTYPE_STATION: case NL80211_IFTYPE_AP: ndev->ieee80211_ptr->iftype = type; break; default: pr_info("owl: invalid interface type %u\n", type); return -EINVAL; } return 0; } ``` ### 初始化 #### vwifi_init 先配置整個 driver 唯一的 context,然後初始化裡面的參數。 ``` owl = kmalloc(sizeof(struct owl_context), GFP_KERNEL); ... mutex_init(&owl->lock); INIT_LIST_HEAD(&owl->vif_list); INIT_LIST_HEAD(&owl->ap_list); ``` 接下來依照 station 數量來建立虛擬網路設備 首先要先建立 nl80211 上用來描述一個的設備結構體 wiphy,通過 owl_cfg80211_add 來建立。 #### owl_cfg80211_add :::info 註解的這部分 `which will call cfg80211_ops->add_iface()`,我去查 [cfg80211_ops](https://www.kernel.org/doc/html/v4.12/driver-api/80211/cfg80211.html) 好像沒有這個成員。 ::: 建立 wiphy 的函式可以用 wiphy_new_nm 或 wiphy_new,差在要不要自己取名。 ``` /* NULL means use the default phy%d naming. */ wiphy = wiphy_new_nm(&owl_cfg_ops, 0, NULL); ``` 設定這個設備支援的 interface type (AP、STA)。 ``` wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_AP); ``` 接著填入這個設備的支援 band 以及可以 scan 的最大數量。 ``` wiphy->bands[NL80211_BAND_2GHZ] = &nf_band_2ghz; wiphy->max_scan_ssids = MAX_PROBED_SSIDS; ``` 設定 signal strength value ``` wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM; ``` 定義在 `/include/net/cfg80211.h` ``` /** * enum cfg80211_signal_type - signal type * * @CFG80211_SIGNAL_TYPE_NONE: no signal strength information available * @CFG80211_SIGNAL_TYPE_MBM: signal strength in mBm (100*dBm) * @CFG80211_SIGNAL_TYPE_UNSPEC: signal strength, increasing from 0 through 100 */ enum cfg80211_signal_type { CFG80211_SIGNAL_TYPE_NONE, CFG80211_SIGNAL_TYPE_MBM, CFG80211_SIGNAL_TYPE_UNSPEC, }; ``` 最後在 cfg80211 註冊這個 wiphy ``` if (wiphy_register(wiphy) < 0) { pr_info("couldn't register wiphy device\n"); goto l_error_wiphy_register; } ``` #### owl_interface_add 接下來要替這個設備建立一個虛擬介面(virtual interface)。 首先先 allocate 一個網路設備 ``` ndev = alloc_netdev(sizeof(struct owl_vif), NDEV_NAME, NET_NAME_ENUM, ether_setup); ``` 第一個參數 sizeof(struct owl_vif) 代表 private data 的空間大小,這邊 private data 是指向該設備的 virtual interface。 然後是基本的網路設備的初始化函式 ether_setup。 ``` void ether_setup(struct net_device *dev) { dev->header_ops = &eth_header_ops; dev->type = ARPHRD_ETHER; dev->hard_header_len = ETH_HLEN; dev->min_header_len = ETH_HLEN; dev->mtu = ETH_DATA_LEN; dev->min_mtu = ETH_MIN_MTU; dev->max_mtu = ETH_DATA_LEN; dev->addr_len = ETH_ALEN; dev->tx_queue_len = DEFAULT_TX_QUEUE_LEN; dev->flags = IFF_BROADCAST|IFF_MULTICAST; dev->priv_flags |= IFF_TX_SKB_SHARING; eth_broadcast_addr(dev->broadcast); } ``` 接下來就是填入 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. ``` /* fill private data of network context. */ vif = ndev_get_owl_vif(ndev); vif->ndev = ndev; /* fill wireless_dev context. * wireless_dev with net_device can be represented as inherited class of * single net_device. */ vif->wdev.wiphy = wiphy; vif->wdev.netdev = ndev; vif->wdev.iftype = NL80211_IFTYPE_STATION; vif->ndev->ieee80211_ptr = &vif->wdev; /* set network device hooks. should implement ndo_start_xmit() at least */ vif->ndev->netdev_ops = &owl_ndev_ops; /* Add here proper net_device initialization */ vif->ndev->features |= NETIF_F_HW_CSUM; ``` 值得一提的地方是設定 MAC address 地方,用的方法是直接將設備名稱複製過去,不過要注意的是要避免使用到 multicast 的 MAC address,也就是說整個 MAC address 最高位的 byte 的最低位 bit 不能是 1,所以這邊就直接設定成 0。 :::info 這邊的 snprintf 的 size 部分,我覺得應該可以是 ETH_ALEN - 1,因為 intf_name[ETH_ALEN] 已經填 0 了。 ::: ``` /* The first byte is '\0' to avoid being a multicast * address (the first byte of multicast addrs is odd). */ char intf_name[ETH_ALEN] = {0}; snprintf(intf_name + 1, ETH_ALEN, "%s%d", NAME_PREFIX, if_idx); memcpy(vif->ndev->dev_addr, intf_name, ETH_ALEN); ``` 最後就註冊這個 net_device 就可以了 ``` if (register_netdev(vif->ndev)) goto l_error_ndev_register; ``` 以上可以參考 [brcmf_cfg80211_request_sta_if](https://elixir.bootlin.com/linux/latest/C/ident/brcmf_cfg80211_request_sta_if) 接下來就是繼續 vif 的初始化 ``` /* Initialize connection information */ memset(vif->bssid, 0, ETH_ALEN); memset(vif->ssid, 0, IEEE80211_MAX_SSID_LEN); memset(vif->req_ssid, 0, IEEE80211_MAX_SSID_LEN); vif->scan_request = NULL; vif->sme_state = SME_DISCONNECTED; vif->conn_time = 0; vif->active_time = 0; vif->disconnect_reason_code = 0; vif->ap = NULL; mutex_init(&vif->lock); ``` 初始化 scan_timeout 計時器 ``` /* Initialize timer of scan_timeout */ timer_setup(&vif->scan_timeout, owl_scan_timeout, 0); ``` Initialize all of a work item ``` INIT_WORK(&vif->ws_connect, owl_connect_routine); INIT_WORK(&vif->ws_disconnect, owl_disconnect_routine); INIT_WORK(&vif->ws_scan, owl_scan_routine); INIT_WORK(&vif->ws_scan_timeout, owl_scan_timeout_work); ``` 最後把該 virtual interface 加入 owl 的 vif->list ``` /* Add vif into global vif_list */ if (mutex_lock_interruptible(&owl->lock)) goto l_error_add_list; list_add_tail(&vif->list, &owl->vif_list); mutex_unlock(&owl->lock); ``` ### net_device_ops > 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](https://static.lwn.net/images/pdf/LDD3/ch17.pdf) 有講最基本的概念 實作的部分參考 [Linux Kernel(16.1)- Network Device Driver, simple snull.](http://nano-chicken.blogspot.com/2016/02/linux-kernel161-network-device-driver.html) #### ndo_open > This function is called when a network device transitions to the up state. owl_ndo_open 的定義如下 ``` static int owl_ndo_open(struct net_device *dev) { netif_start_queue(dev); return 0; } ``` netif_start_queue 用來表示這個設備允許傳輸 ``` /** * netif_start_queue - allow transmit * @dev: network device * * Allow upper layers to call the device hard_start_xmit routine. */ static inline void netif_start_queue(struct net_device *dev) { netif_tx_start_queue(netdev_get_tx_queue(dev, 0)); } ``` #### owl_ndo_stop > This function is called when a network device transitions to the down state. 這裡做的事比較單純,因為設備停止傳輸了,所以也要把收到的封包刪除。 ``` list_for_each_entry_safe (pkt, is, &vif->rx_queue, list) { list_del(&pkt->list); kfree(pkt); } ``` 最後再停止接收封包 ``` netif_stop_queue - stop transmitted packets ``` #### ndo_get_stats > 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 呼叫。 ``` static struct net_device_stats *owl_ndo_get_stats(struct net_device *dev) { struct owl_vif *vif = ndev_get_owl_vif(dev); return &vif->stats; } ``` #### ndo_start_xmit > 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 和 第二層的標頭 ``` struct owl_vif *vif = ndev_get_owl_vif(dev); struct owl_vif *dest_vif = NULL; struct ethhdr *eth_hdr = (struct ethhdr *) skb->data; ``` 接下來會判斷自己是 STA 或 AP,各自會有不同的實作。 這邊的邏輯很簡單,STA 傳輸的對象只可能是目前連線中的 AP(multicast/broadcast 都會先傳給 AP 轉傳),所以就直接傳。 ``` if (vif->wdev.iftype == NL80211_IFTYPE_STATION) { if (vif->ap && vif->ap->ap_enabled) { dest_vif = vif->ap; if (__owl_ndo_start_xmit(vif, dest_vif, skb)) count++; } } ``` 如果是 AP,目前只有支援 broadcast 跟 unicast :::info multicast 不知道有沒有支援? ::: broadcast: ``` if (is_broadcast_ether_addr(eth_hdr->h_dest)) { list_for_each_entry (dest_vif, &vif->bss_list, bss_list) { /* Don't send broadcast packet back * to the source interface. */ if (ether_addr_equal(eth_hdr->h_source, dest_vif->ndev->dev_addr)) continue; if (__owl_ndo_start_xmit(vif, dest_vif, skb)) count++; } } ``` unicast: ``` /* The packet is unicasting */ else { list_for_each_entry (dest_vif, &vif->bss_list, bss_list) { if (ether_addr_equal(eth_hdr->h_dest, dest_vif->ndev->dev_addr)) { if (__owl_ndo_start_xmit(vif, dest_vif, skb)) count++; break; } } } ``` 最後就是判斷封包是否 drop,以及最後要把封包 free 掉。 ``` if (!count) vif->stats.tx_dropped++; /* Don't forget to cleanup skb, as its ownership moved to xmit callback. */ dev_kfree_skb(skb); ``` #### __owl_ndo_start_xmit 這個函式負責把封包的資料取出 一開始先配置 owl_packet 所需空間 ``` pkt = kmalloc(sizeof(struct owl_packet), GFP_KERNEL); ``` 然後從 sk_buff 取資料填入 owl_packet ``` datalen = skb->len; memcpy(pkt->data, skb->data, datalen); pkt->datalen = datalen; ``` 接下來把封包放入目的端的 virtual interface 的 rx_queue ``` /* enqueue packet to destination vif's rx_queue */ if (mutex_lock_interruptible(&dest_vif->lock)) goto l_error_before_rx_queue; list_add_tail(&pkt->list, &dest_vif->rx_queue); mutex_unlock(&dest_vif->lock); ``` 然後是在來源端的 virtual interface 填入相關資料 ``` if (mutex_lock_interruptible(&vif->lock)) goto l_erorr_after_rx_queue; /* Update interface statistics */ vif->stats.tx_packets++; vif->stats.tx_bytes += datalen; vif->active_time = jiffies; mutex_unlock(&vif->lock); ``` 最後還要處理接收封包 ``` /* Directly send to rx_queue, simulate the rx interrupt */ owl_rx(dest_vif->ndev); ``` #### owl_rx 處理 rx_queue 收到的封包,將它轉成 skb_buff 的格式。 先處理第一個收到封包 ``` if (mutex_lock_interruptible(&vif->lock)) goto pkt_free; pkt = list_first_entry(&vif->rx_queue, struct owl_packet, list); vif->stats.rx_packets++; vif->stats.rx_bytes += pkt->datalen; vif->active_time = jiffies; mutex_unlock(&vif->lock); ``` 接下來可以先看到這篇關於 [On the alignment of IP packets](https://lwn.net/Articles/89597/) 的說明,可以了解因為 Ethernet Header(14B),導致後面的 IP Header(16B) 無法對齊 four-byte boundary,所以才要多了前面的 2B。 ![](https://hackmd.io/_uploads/BJePcxtH2.png) skb_reserve 用來移動 data、tail 指標。 skb_put 往下移動 tail 指標用來增加 data 的空間,並回傳移動前的 tail 指標位置。 最後將該封包從 rx_queue 移除並釋放空間。 ``` /* Put raw packet into socket buffer */ skb = dev_alloc_skb(pkt->datalen + 2); if (!skb) { pr_info("owl rx: low on mem - packet dropped\n"); vif->stats.rx_dropped++; goto pkt_free; } skb_reserve(skb, 2); /* align IP on 16B boundary */ memcpy(skb_put(skb, pkt->datalen), pkt->data, pkt->datalen); list_del(&pkt->list); kfree(pkt); ``` AP 處理 multicast/broadcast packet 的部分,會轉傳給除了來源 STA 外的所有 STA。 因為 broadcast 是 multicast 的一個特例,所以可以用 is_multicast_ether_addr 來判斷,若是則將 skb 複製到 skb1。 ``` /* Receiving a multicast/broadcast packet, send it to every * STA except the source STA, and pass it to protocol stack. */ if (is_multicast_ether_addr(eth_hdr->h_dest)) { pr_info("owl: is_multicast_ether_addr\n"); skb1 = skb_copy(skb, GFP_KERNEL); } ``` 如果 AP 收到的封包為 unicast,會有兩種可能(給 AP 的、給 BSS 內的某 STA),這邊先處理第二種可能,一樣先把 skb 複製到 skb1,然後把 skb assign 為 NULL。 ``` /* Receiving a unicast packet */ else { /* The packet is not for AP itself, send it to destination * STA, and do not pass it to procotol stack. */ if (!ether_addr_equal(eth_hdr->h_dest, vif->ndev->dev_addr)) { skb1 = skb; skb = NULL; } } ``` 接著先處理轉傳的部分(送給其他 STA) ``` if (skb1) { pr_info("owl: AP %s relay:\n", vif->ndev->name); owl_ndo_start_xmit(skb1, vif->ndev); } /* Nothing to pass to protocol stack */ if (!skb) return; ``` 處理第一種可能,這個封包就是給自己(STA 或 AP)的就送給 protocol stack 處理 ``` /* Pass the skb to protocol stack */ skb->dev = dev; skb->protocol = eth_type_trans(skb, dev); skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */ #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 18, 0) netif_rx_ni(skb); #else netif_rx(skb); #endif return; ``` ### signal TODO