cfg80211 & wext

Posted by Vector Blog on September 8, 2017

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);
};