1. 简介
Linux 平台目前常用针对无线网络设备的API有两套 wext (Wireless Extensions) 和 nl80211 & cfg80211 , 其主要区别是 wext 通过 ioctl 的方式来控制无线网卡驱动, 后者使用 netlink .
这里主要介绍 nl80211 和 cfg80211 , 其中 , cfg80211用于驱动开发, 而 nl80211 API供用户空间进程使用以操作那些利用cfg80211 API开发的无线网卡驱动。 使用 nl80211 的 tool 包括 : iw , crda , hostapd , wpa_supplicant(with -Dnl80211)
这里 wpas, iw 和 hostapd 通过 libnl 提供的接口向 cfg80211 发送消息, 其消息中的 cmd 的数据参数等由 nl80211 定义, 目前做的几个 Qcom/BCM wifi chip 的 android project 都属于 FullMAC (硬件管理MLME的无线网卡类型) , 所以 cfg80211 会直接调用到 wlan driver 的接口, 其主要调用通过 cfg80211_ops 结构体。
2. nl80211 & cfg80211
简单来说, nl80211的核心就是通过 netlink 机制向 Kernel 中的 cfg80211 无线网卡驱动发送特定的消息。 只不过这些消息的类型、 参数等都由nl80211.h定义。
2.1 scan 举例
以下通过 nl80211 触发driver 扫描为例 :
@ wpa_supplicant/src/drivers/driver_nl80211_scan.c
int wpa_driver_nl80211_scan(struct i802_bss *bss,
struct wpa_driver_scan_params *params)
{
struct nl_msg *msg = NULL;
......
// 创建nl80211消息, 其中NL80211_CMD_TRIGGER_SCAN是Nl80211定义的命令, 用于触发网络扫描
msg = nl80211_scan_common(bss, NL80211_CMD_TRIGGER_SCAN, params);
......
// 发送netlink消息
ret = send_and_recv_msgs(drv, msg, NULL, NULL);
}
static struct nl_msg *
nl80211_scan_common(struct i802_bss *bss, u8 cmd,
struct wpa_driver_scan_params *params)
{
struct nl_msg *msg;
msg = nl80211_cmd_msg(bss, 0, cmd);
struct nl_msg * nl80211_cmd_msg(struct i802_bss *bss, int flags, uint8_t cmd)
{
struct nl_msg *msg;
// 分配 nl_msg
msg = nlmsg_alloc();
// 内部调用 genlmsg_put 填充 netlink 首部和 genl 首部中的 目的genl family id , fag 和 cmd
if (!nl80211_cmd(bss->drv, msg, flags, cmd) ||
// 内部调用 nla_put_u32 填充nlattr type 为NL80211_ATTR_IFINDEX 和 data
nl80211_set_iface_id(msg, bss) < 0) {
nlmsg_free(msg);
return NULL;
}
return msg;
}
...... // 其他消息处理
return msg;
}
由上面的例子可知, nl80211其实就是利用netlink机制将一些802.11相关的命令和参数发送给驱动去执行。 这些命令和参数信息可通过nl80211头文件查询 :
@ wpa_supplicant/src/drivers/nl80211_copy.h
// 支持的所有 cmd
enum nl80211_commands {
NL80211_CMD_UNSPEC //unspecified command to catch errors
NL80211_CMD_GET_WIPHY //request information about a wiphy or dump request to get a list of all present wiphys.
......
NL80211_CMD_GET_SCAN //get scan results
NL80211_CMD_TRIGGER_SCAN //trigger a new scan with the given parameters
......
__NL80211_CMD_AFTER_LAST,
NL80211_CMD_MAX = __NL80211_CMD_AFTER_LAST - 1
}
// 支持的所有 attributes
enum nl80211_attrs {
/* don't change the order or add anything between, this is ABI! */
NL80211_ATTR_UNSPEC,
NL80211_ATTR_WIPHY,
NL80211_ATTR_WIPHY_NAME,
NL80211_ATTR_IFINDEX,
NL80211_ATTR_IFNAME,
NL80211_ATTR_IFTYPE,
......
__NL80211_ATTR_AFTER_LAST,
NUM_NL80211_ATTR = __NL80211_ATTR_AFTER_LAST,
NL80211_ATTR_MAX = __NL80211_ATTR_AFTER_LAST - 1
};
上面给 family name 为 “nl80211” 的 gerneric netlink 发送了一个 cmd 为 NL80211_CMD_TRIGGER_SCAN 的消息 , 我们来看 cfg80211 里面的代码 :
@ kernel/net/wireless/nl80211.c
static const struct genl_ops nl80211_ops[] = {
......
{
.cmd = NL80211_CMD_TRIGGER_SCAN,
.doit = nl80211_trigger_scan,
.policy = nl80211_policy,
.flags = GENL_ADMIN_PERM,
.internal_flags = NL80211_FLAG_NEED_WDEV_UP |
NL80211_FLAG_NEED_RTNL,
},
......
}
static int nl80211_trigger_scan(struct sk_buff *skb, struct genl_info *info)
{
// 只看关键代码
err = rdev_scan(rdev, request);
......
}
static inline int rdev_scan(struct cfg80211_registered_device *rdev,
struct cfg80211_scan_request *request)
{
int ret;
ret = rdev->ops->scan(&rdev->wiphy, request);
return ret;
}
这里会在调用到已注册的 cfg80211 device 的 cfg80211_ops 的scan 函数 , 在看 wlan driver 中的定义
static struct cfg80211_ops wlan_hdd_cfg80211_ops =
{
.......
.scan = wlan_hdd_cfg80211_scan,
}
2.2 libnl 简介
鉴于netlink的复杂性, 开源世界提供了几个较为完备的基于netlink编程的框架, 其中最著名的就是libnl :
· libnl-route: 用于和Kernel中的Routing子系统交互。
· libnl-nf: 用于和Kernel中的Netfilter子系统交互。
· libnl-genl: 用于和Kernel中的Generic Netlink模块交互。
在 Android 中移植了精简了libnl 和libnl-genl 的项目代码 external/libnl
libnl 重新封装了netlink原有的API , 其使用时必须分配一个nl_sock结构体 . 以下介绍部分会用到的 libnl API :
/* 分配和释放 nl_sock */
struct nl_sock *nl_socket_alloc(void)
void nl_socket_free(struct nl_sock *sk)
// nl_connet内部将通过bind函数将netlink socket和protocol对应的模块进行绑定
int nl_connect(struct nl_sock *sk, int protocol)
// 为nl_sock对象设置一个回调函数, 当该socket上收到消息后, 就会回调此函数进行处理
void nl_socket_set_cb(struct nl_sock *sk, struct nl_cb *cb);
// 获取该nl_sock设置的回调函数信息
struct nl_cb *nl_socket_get_cb(const struct nl_sock *sk);
// 下面这两个函数计算netlink消息体中对应部分的长度
int nlmsg_size(int payloadlen); // 请参考图来理解这两个函数返回值的意义
int nlmsg_total_size(int payloadlen);
/* nl_msg 的分配和释放 */
struct nl_msg *nlmsg_alloc(void);
void nlmsg_free(struct nl_msg *msg);
;
// nl_msg内部肯定会指向一个netlink消息头实例, 下面这个函数用于填充netlink消息头
struct nlmsghdr *nlmsg_put(struct nl_msg *msg,
uint32_t port, uint32_t seqnr,
int nlmsg_type, int payload, int nlmsg_flags);
// 直接发送netlink消息
int nl_sendto (struct nl_sock *sk, void *buf, size_t size)
// 发送nl_msg消息
int nl_send (struct nl_sock *sk, struct nl_msg *msg)
int nl_send_simple(struct nl_sock *sk, int type,
int flags,void *buf, size_t size);
。
// 核心接收函数。 nla参数用于存储发送端的地址信息。 creds用于存储权限相关的信息
int nl_recv(struct nl_sock *sk, struct sockaddr_nl *nla,
unsigned char **buf, struct ucred **creds)
// 内部通过nl_recv接收消息, 然后通过cb回调结构体中的回调函数传给接收者
int nl_recvmsgs (struct nl_sock *sk, struct nl_cb *cb)
** libnl-genl API 介绍**
libnl-genl 基于libnl , 封装了对generic netlink模块的处理 :
// 根据family name字符串去查询family, 该函数内部实现将发送查询消息给Controller
int genl_ctrl_resolve (struct nl_sock *sk, const char *name)
// 和libnl的nl_connect类型, 只不过协议类型为GENERIC_NETLINK
int genl_connect (struct nl_sock *sk)
// genlmsg_put用于填充图中的nlmsghdr、 genlmsghder和用户自定义的消息头。 详细内容见下文
void* genlmsg_put (struct nl_msg *msg, uint32_t port,
uint32_t seq, int family, int hdrlen,
int flags, uint8_t cmd, uint8_t version)
// 用于获取genl消息中携带的nlattr内容
struct nlattr* genlmsg_attrdata(const struct genlmsghdr *gnlh,int hdrlen)
2.3 cfg80211
在 cfg80211 中每个设备的基础结构体为 wiphy
@ kernel/include/net/cfg80211.h
struct wiphy {
u8 perm_addr[ETH_ALEN]; // 固定的设备mac地址
/* 如果设备支持使用掩码多mac地址,则可根据掩码将相应的标志位置1
例如,最后四位可变,则该成员设置为 00-00-00-00-00-0f */
u8 addr_mask[ETH_ALEN]; //
/* 如果设备有多个mac地址,这这个指针指向地址列表,第一个地址就是默认perm_addr */
struct mac_address *addresses;
......
};
相关接口
// 创建 wiphy
struct wiphy *wiphy_new(const struct cfg80211_ops *ops, int sizeof_priv);
// driver 需要定义自己的 cfg80211_ops
struct cfg80211_ops {
int (*suspend)(struct wiphy *wiphy, struct cfg80211_wowlan *wow);
int (*resume)(struct wiphy *wiphy);
......
int (*scan)(struct wiphy *wiphy,
struct cfg80211_scan_request *request);
void (*abort_scan)(struct wiphy *wiphy, struct wireless_dev *wdev);
int (*auth)(struct wiphy *wiphy, struct net_device *dev,
struct cfg80211_auth_request *req);
int (*assoc)(struct wiphy *wiphy, struct net_device *dev,
struct cfg80211_assoc_request *req);
int (*deauth)(struct wiphy *wiphy, struct net_device *dev,
struct cfg80211_deauth_request *req);
int (*disassoc)(struct wiphy *wiphy, struct net_device *dev,
struct cfg80211_disassoc_request *req);
int (*connect)(struct wiphy *wiphy, struct net_device *dev,
struct cfg80211_connect_params *sme);
int (*disconnect)(struct wiphy *wiphy, struct net_device *dev,
u16 reason_code);
......
}
// 为wiphy设置设备指针
void set_wiphy_dev(struct wiphy * wiphy, struct device * dev)
// 在cfg80211 中注册 wiphy
int wiphy_register(struct wiphy * wiphy)
// 在cfg80211中注销一个wiphy
void wiphy_unregister(struct wiphy * wiphy)
// 释放创建好的 wiphy
void wiphy_free(struct wiphy * wiphy)
// 获取wiphy的私有数据
void * wiphy_priv(struct wiphy * wiphy)
3. WEXT
wext 与 nl80211 的主要不同之处在于用户层与 driver 通信的方式为 ioctrl , 使用该接口的tool 有 ifconfig , iwpriv 等。
先来看一下 iwpriv 是如何下指令给 driver
int
main(int argc,
char ** argv)
{
skfd = iw_sockets_open();
=> sock = socket(AF_INET, SOCK_DGRAM, 0);
......
set_private(skfd, argv + 2, argc - 2, argv[1]);
}
static inline int
set_private(int skfd, /* Socket */
char * args[], /* Command line args */
int count, /* Args count */
char * ifname) /* Dev name */
{
iwprivargs * priv;
int number; /* Max of private ioctl */
int ret;
/* 获取wireless网卡所能处理的所有 wlan_private_args 类型 */
number = iw_get_priv_info(skfd, ifname, &priv);
/* Is there any ? */
if(number <= 0)
{
......
return(-1);
}
/* Do it */
ret = set_private_cmd(skfd, args + 1, count - 1, ifname, args[0],
priv, number);
}
set_private 中先查需该设备支援的 private ioctls , 如不为 0 则在来执行 , 这里最后都是调用 ioctrl , 这里以 iw_get_priv_info 来看一下流程
int
iw_get_priv_info(int skfd,
const char * ifname,
iwprivargs ** ppriv)
{
struct iwreq wrq;
iw_get_ext(skfd, ifname, SIOCGIWPRIV, &wrq)
static inline int
iw_get_ext(int skfd, /* Socket to the kernel */
const char * ifname, /* Device name */
int request, /* WE ID */
struct iwreq * pwrq) /* Fixed part of the request */
{
strncpy(pwrq->ifr_name, ifname, IFNAMSIZ);
return(ioctl(skfd, request, pwrq));
}
到这里向 socket 句柄发送 ioctrl , cmd 为 SIOCGIWPRIV , 参数为结构体 iwreq 其中会包含网络设备的 ifname . 下面在来看 kernel 中如何处理
@ kernel/net/core/dev_ioctl.c
=> dev_ioctl
default:
/* Take care of Wireless Extensions */
if (cmd >= SIOCIWFIRST && cmd <= SIOCIWLAST)
return wext_handle_ioctl(net, &ifr, cmd, arg);
@ kernel/net/wireless/wext-core.c
=> wext_handle_ioctl
struct iw_request_info info = { .cmd = cmd, .flags = 0 };
ret = wext_ioctl_dispatch(net, ifr, cmd, &info,
ioctl_standard_call,
ioctl_private_call);
// 当为执行成功且 cmd 为get 时将结构返回给上层
if (ret >= 0 &&
IW_IS_GET(cmd) && // IW_IS_GET(cmd) ((cmd) & 0x1)
copy_to_user(arg, ifr, sizeof(struct iwreq)) )
=> wext_ioctl_dispatch
=> wireless_process_ioctl
仔细看一下 wireless_process_ioctl
static int wireless_process_ioctl(struct net *net, struct ifreq *ifr,
unsigned int cmd,
struct iw_request_info *info,
wext_ioctl_func standard,
wext_ioctl_func private)
{
dev = __dev_get_by_name(net, ifr->ifr_name);
/* 所以这里 cmd 为 SIOCGIWPRIV 时是执行这里
* 实际执行就是其参数 iw_handler_get_private
* 其中会填充 iwreq_data 的 data.length 为
* wireless_handlers->num_private_args
* pointer 指向 wireless_handlers->private_args*/
if (cmd == SIOCGIWPRIV && dev->wireless_handlers)
return standard(dev, iwr, cmd, info,
iw_handler_get_private);
/* New driver API : try to find the handler */
handler = get_handler(dev, cmd);
==> get_handler
#ifdef CONFIG_WIRELESS_EXT
if (dev->wireless_handlers)
handlers = dev->wireless_handlers;
#endif
/* Try as a standard command */
index = IW_IOCTL_IDX(cmd);
if (index < handlers->num_standard)
return handlers->standard[index];
if (handler) {
/* Standard and private are not the same */
if (cmd < SIOCIWFIRSTPRIV)
return standard(dev, iwr, cmd, info, handler);
else if (private)
return private(dev, iwr, cmd, info, handler);
}
/* Old driver API : call driver ioctl handler */
if (dev->netdev_ops->ndo_do_ioctl)
return dev->netdev_ops->ndo_do_ioctl(dev, ifr, cmd);
}
这里只关注新的 driver API , 回头来看这里的 standard 和 private 是前面传进来的函数指针 ioctl_standard_call 和 ioctl_private_call , 在根据cmd 为 standard 或者 private 分别调用 。他们最终调用的都是 wireless_handlers 中的 standard 和 private 数组中的相应函数 。
所以上面 iw_get_priv_info 直接返回driver 中 wireless_handlers->num_private_args , iwprivargs 指向从driver 中copy 的 wireless_handlers->private_args , 其为 iw_priv_args 结构体数组
struct iw_priv_args {
__u32 cmd;
__u16 set_args;
__u16 get_args;
char name[IFNAMSIZ];
};
其中会包括每条command 的 cmd , set/get 参数个数 和 cmd 的对应 string name. 接下来的 set_private_cmd 中根据用户的 command 来匹配对应的 cmd 然后iotrl 给driver .
综上, driver 中只需指定 net_dev->wireless_handlers iw_handler_def 结构体, 即可在用户空间调用 iwpriv 或者 ifconfig 调用实现的功能
struct iw_handler_def {
const iw_handler * standard;
__u16 num_standard;
__u16 num_private;
__u16 num_private_args;
const iw_handler * private;
const struct iw_priv_args * private_args;
struct iw_statistics* (*get_wireless_stats)(struct net_device *dev);
};