wpa_supplicant与kernel的接口

本文介绍了WPA驱动的定义及实现方式,包括不同类型的驱动接口及其选择过程。此外,还详细阐述了用户态与内核态之间的交互机制,特别是ioctl和netlink的使用。最后,深入解析了SCAN流程,从用户请求到内核处理的完整路径。

1. 接口定义实现wpa_drivers

    wpa_drivers的定义如下:

struct wpa_driver_ops *wpa_drivers[] =
{
#ifdef CONFIG_DRIVER_WEXT
	&wpa_driver_wext_ops,  // 我的系统使用的这个老的接口
#endif /* CONFIG_DRIVER_WEXT */
#ifdef CONFIG_DRIVER_NL80211   // 现在流行的NL80211接口
	&wpa_driver_nl80211_ops,
#endif /* CONFIG_DRIVER_NL80211 */
#ifdef CONFIG_DRIVER_HOSTAP
	&wpa_driver_hostap_ops,
#endif /* CONFIG_DRIVER_HOSTAP */
#ifdef CONFIG_DRIVER_MADWIFI
	&wpa_driver_madwifi_ops,
#endif /* CONFIG_DRIVER_MADWIFI */
#ifdef CONFIG_DRIVER_BROADCOM
	&wpa_driver_broadcom_ops,
#endif /* CONFIG_DRIVER_BROADCOM */
#ifdef CONFIG_DRIVER_BSD
	&wpa_driver_bsd_ops,
#endif /* CONFIG_DRIVER_BSD */
#ifdef CONFIG_DRIVER_NDIS
	&wpa_driver_ndis_ops,
#endif /* CONFIG_DRIVER_NDIS */
#ifdef CONFIG_DRIVER_WIRED
	&wpa_driver_wired_ops,
#endif /* CONFIG_DRIVER_WIRED */
#ifdef CONFIG_DRIVER_TEST
	&wpa_driver_test_ops,
#endif /* CONFIG_DRIVER_TEST */
#ifdef CONFIG_DRIVER_RALINK
	&wpa_driver_ralink_ops,
#endif /* CONFIG_DRIVER_RALINK */
#ifdef CONFIG_DRIVER_OSX
	&wpa_driver_osx_ops,
#endif /* CONFIG_DRIVER_OSX */
#ifdef CONFIG_DRIVER_IPHONE
	&wpa_driver_iphone_ops,
#endif /* CONFIG_DRIVER_IPHONE */
#ifdef CONFIG_DRIVER_ROBOSWITCH
	&wpa_driver_roboswitch_ops,
#endif /* CONFIG_DRIVER_ROBOSWITCH */
#ifdef CONFIG_DRIVER_ATHEROS
	&wpa_driver_atheros_ops,
#endif /* CONFIG_DRIVER_ATHEROS */
#ifdef CONFIG_DRIVER_NONE
	&wpa_driver_none_ops,
#endif /* CONFIG_DRIVER_NONE */
	NULL
};


     具体选择哪一个driver,由wpa_supplicant的命令参数决定,如我的如下:
     在init.myboard.rc中定义:

service wpa_supplicant /system/bin/wpa_supplicant \
    -Dwext -iwlan0 -c/data/misc/wifi/wpa_supplicant.conf
    #-Dnl80211 -iwlan0 -puse_p2p_group_interface=1 -e/data/misc/wifi/entropy.bin
    #   we will start as root and wpa_supplicant will switch to user wifi
    #   after setting up the capabilities required for WEXT
    #   user wifi
    #   group wifi inet keystore
    class main
    socket wpa_wlan0 dgram 660 wifi wifi
    disabled
    oneshot


     由上可见,我的选择是wext, 即选择了:wpa_driver_wext_ops。具体选择在以下函数中实现,并最后保存在wpa_supplicant->driver中,以供在wpa_drv_scan中使用。
static int wpa_supplicant_set_driver(struct wpa_supplicant *wpa_s,
				     const char *name)
{
	int i;
	size_t len;
	const char *pos, *driver = name;

	if (wpa_s == NULL)
		return -1;

       wpa_msg(wpa_s,MSG_ERROR,"***MY_WIFI:%s,name=%s\n",__FUNCTION__,name);
    
	if (wpa_drivers[0] == NULL) {
		wpa_msg(wpa_s, MSG_ERROR, "No driver interfaces build into "
			"wpa_supplicant");
		return -1;
	}

	if (name == NULL) {
		/* default to first driver in the list */
		wpa_s->driver = wpa_drivers[0];
		wpa_s->global_drv_priv = wpa_s->global->drv_priv[0];
		return 0;
	}

	do {
		pos = os_strchr(driver, ',');
		if (pos)
			len = pos - driver;
		else
			len = os_strlen(driver);

		for (i = 0; wpa_drivers[i]; i++) {
			if (os_strlen(wpa_drivers[i]->name) == len &&
			    os_strncmp(driver, wpa_drivers[i]->name, len) ==
			    0) {
				wpa_s->driver = wpa_drivers[i];  // 根据name进行匹配,并最后保存到wpa_supplicant->dirver中
				wpa_s->global_drv_priv =
					wpa_s->global->drv_priv[i];
				return 0;
			}
		}

		driver = pos + 1;
	} while (pos);

	wpa_msg(wpa_s, MSG_ERROR, "Unsupported driver '%s'", name);
	return -1;
}


2. 接口操作函数实现

2.1 用户态实现

    用户态实现的操作函数如下:

    实现代码见:/external/wpa_supplicant_8/wpa_supplicant/src/drivers/driver_wext.c

const struct wpa_driver_ops wpa_driver_wext_ops = {
	.name = "wext",
	.desc = "Linux wireless extensions (generic)",
	.get_bssid = wpa_driver_wext_get_bssid,
	.get_ssid = wpa_driver_wext_get_ssid,
#ifdef WAPI
	.set_wapi = wpa_driver_wext_set_wapi,
#endif
	.set_key = wpa_driver_wext_set_key,
	.set_countermeasures = wpa_driver_wext_set_countermeasures,
	.scan2 = wpa_driver_wext_scan,
	.get_scan_results2 = wpa_driver_wext_get_scan_results,
	.deauthenticate = wpa_driver_wext_deauthenticate,
	.disassociate = wpa_driver_wext_disassociate,
	.associate = wpa_driver_wext_associate,
	.init = wpa_driver_wext_init, // 初始ioctl socket, netlink socket
	.deinit = wpa_driver_wext_deinit,
	.add_pmkid = wpa_driver_wext_add_pmkid,
	.remove_pmkid = wpa_driver_wext_remove_pmkid,
	.flush_pmkid = wpa_driver_wext_flush_pmkid,
	.get_capa = wpa_driver_wext_get_capa,
	.set_operstate = wpa_driver_wext_set_operstate,
	.get_radio_name = wext_get_radio_name,
#ifdef ANDROID
	.signal_poll = wpa_driver_signal_poll,
	.driver_cmd = wpa_driver_wext_driver_cmd, // 对应驱动中的 cfg80211_wext_setpriv
#endif
};


.driver_cmd处理以DRIVER开始的命令,如:

    DRIVER MACADDR

    DRIVER BTCOEXSCAN-STOP

    DRIVER RXFILTER-ADD 3

    DRIVER RXFILTER-START

    DRIVER RXFILTER-STOP

    DRIVER RXFILTER-REMOVE 2

    DRIVER RXFILTER-START

    DRIVER SETBAND 0

    DRIVER SCAN-ACTIVE

    DRIVER SCAN-PASSIVE

   执行流程如下所示:

    wpa_supplicant_ctrl_iface_process-> (根据命令字符串调用对应的函数)

      wpa_supplicant_driver_cmd->

        wpa_drv_driver_cmd->

          wpa_s->driver->driver_cmd->

            wpa_driver_wext_driver_cmd-> (User)

            ...

            cfg80211_wext_setpriv(Kernel)


    

2.2 Kernel态实现      

      Kernel态实现的操作函数如下:

      实现代码见:net/wireless/wext_compat.c

static const iw_handler cfg80211_handlers[] = {
	[IW_IOCTL_IDX(SIOCGIWNAME)]	= (iw_handler) cfg80211_wext_giwname,
	[IW_IOCTL_IDX(SIOCSIWFREQ)]	= (iw_handler) cfg80211_wext_siwfreq,
	[IW_IOCTL_IDX(SIOCGIWFREQ)]	= (iw_handler) cfg80211_wext_giwfreq,
	[IW_IOCTL_IDX(SIOCSIWMODE)]	= (iw_handler) cfg80211_wext_siwmode,
	[IW_IOCTL_IDX(SIOCGIWMODE)]	= (iw_handler) cfg80211_wext_giwmode,
	[IW_IOCTL_IDX(SIOCGIWRANGE)]	= (iw_handler) cfg80211_wext_giwrange,
	[IW_IOCTL_IDX(SIOCSIWAP)]	= (iw_handler) cfg80211_wext_siwap,
	[IW_IOCTL_IDX(SIOCGIWAP)]	= (iw_handler) cfg80211_wext_giwap,
	[IW_IOCTL_IDX(SIOCSIWMLME)]	= (iw_handler) cfg80211_wext_siwmlme,
	[IW_IOCTL_IDX(SIOCSIWSCAN)]	= (iw_handler) cfg80211_wext_siwscan,
	[IW_IOCTL_IDX(SIOCGIWSCAN)]	= (iw_handler) cfg80211_wext_giwscan,
	[IW_IOCTL_IDX(SIOCSIWESSID)]	= (iw_handler) cfg80211_wext_siwessid,
	[IW_IOCTL_IDX(SIOCGIWESSID)]	= (iw_handler) cfg80211_wext_giwessid,
	[IW_IOCTL_IDX(SIOCSIWRATE)]	= (iw_handler) cfg80211_wext_siwrate,
	[IW_IOCTL_IDX(SIOCGIWRATE)]	= (iw_handler) cfg80211_wext_giwrate,
	[IW_IOCTL_IDX(SIOCSIWRTS)]	= (iw_handler) cfg80211_wext_siwrts,
	[IW_IOCTL_IDX(SIOCGIWRTS)]	= (iw_handler) cfg80211_wext_giwrts,
	[IW_IOCTL_IDX(SIOCSIWFRAG)]	= (iw_handler) cfg80211_wext_siwfrag,
	[IW_IOCTL_IDX(SIOCGIWFRAG)]	= (iw_handler) cfg80211_wext_giwfrag,
	[IW_IOCTL_IDX(SIOCSIWTXPOW)]	= (iw_handler) cfg80211_wext_siwtxpower,
	[IW_IOCTL_IDX(SIOCGIWTXPOW)]	= (iw_handler) cfg80211_wext_giwtxpower,
	[IW_IOCTL_IDX(SIOCSIWRETRY)]	= (iw_handler) cfg80211_wext_siwretry,
	[IW_IOCTL_IDX(SIOCGIWRETRY)]	= (iw_handler) cfg80211_wext_giwretry,
	[IW_IOCTL_IDX(SIOCSIWENCODE)]	= (iw_handler) cfg80211_wext_siwencode,
	[IW_IOCTL_IDX(SIOCGIWENCODE)]	= (iw_handler) cfg80211_wext_giwencode,
	[IW_IOCTL_IDX(SIOCSIWPOWER)]	= (iw_handler) cfg80211_wext_siwpower,
	[IW_IOCTL_IDX(SIOCGIWPOWER)]	= (iw_handler) cfg80211_wext_giwpower,
	[IW_IOCTL_IDX(SIOCSIWGENIE)]	= (iw_handler) cfg80211_wext_siwgenie,
	[IW_IOCTL_IDX(SIOCSIWAUTH)]	= (iw_handler) cfg80211_wext_siwauth,
	[IW_IOCTL_IDX(SIOCGIWAUTH)]	= (iw_handler) cfg80211_wext_giwauth,
	[IW_IOCTL_IDX(SIOCSIWENCODEEXT)]= (iw_handler) cfg80211_wext_siwencodeext,
	[IW_IOCTL_IDX(SIOCSIWPMKSA)]	= (iw_handler) cfg80211_wext_siwpmksa,
        [IW_IOCTL_IDX(SIOCSIWPRIV)] = (iw_handler)cfg80211_wext_setpriv
};

const struct iw_handler_def cfg80211_wext_handler = {
	.num_standard		= ARRAY_SIZE(cfg80211_handlers),
	.standard		= cfg80211_handlers,
	.get_wireless_stats = cfg80211_wireless_stats,
};


2.3 用户态与Kernel态的交互

      用户态向Kernel发送请求时,通过ioctl来实现。

      Kernel用户态发送事件通知,通过netlink来实现。

     其交互的初始化在wpa_driver_wext_init中实现,其代码如下:

/**
 * wpa_driver_wext_init - Initialize WE driver interface
 * @ctx: context to be used when calling wpa_supplicant functions,
 * e.g., wpa_supplicant_event()
 * @ifname: interface name, e.g., wlan0
 * Returns: Pointer to private data, %NULL on failure
 */
void * wpa_driver_wext_init(void *ctx, const char *ifname) // 我的ifname为wlan0
{
	struct wpa_driver_wext_data *drv;
	struct netlink_config *cfg;
	struct rfkill_config *rcfg;
	char path[128];
	struct stat buf;

        wpa_printf(MSG_ERROR,"***MY_WIFI:%s,ifname=%s\n",__FUNCTION__,ifname);
    
	drv = os_zalloc(sizeof(*drv));
	if (drv == NULL)
		return NULL;
	drv->ctx = ctx;
	os_strlcpy(drv->ifname, ifname, sizeof(drv->ifname));

	os_snprintf(path, sizeof(path), "/sys/class/net/%s/phy80211", ifname);
	if (stat(path, &buf) == 0) {
		wpa_printf(MSG_DEBUG, "WEXT: cfg80211-based driver detected");
		drv->cfg80211 = 1;
		wext_get_phy_name(drv);
	}

	drv->ioctl_sock = socket(PF_INET, SOCK_DGRAM, 0);  // 此drv->ioctl_sock用作为ioctl命令的fd
	if (drv->ioctl_sock < 0) {
		perror("socket(PF_INET,SOCK_DGRAM)");
		goto err1;
	}

	cfg = os_zalloc(sizeof(*cfg));
	if (cfg == NULL)
		goto err1;
	cfg->ctx = drv;
	cfg->newlink_cb = wpa_driver_wext_event_rtm_newlink;
	cfg->dellink_cb = wpa_driver_wext_event_rtm_dellink;
	drv->netlink = netlink_init(cfg);  // 初始化netlink,并注册事件接收函数
	if (drv->netlink == NULL) {
		os_free(cfg);
		goto err2;
	}

	rcfg = os_zalloc(sizeof(*rcfg));
	if (rcfg == NULL)
		goto err3;
	rcfg->ctx = drv;
	os_strlcpy(rcfg->ifname, ifname, sizeof(rcfg->ifname));
	rcfg->blocked_cb = wpa_driver_wext_rfkill_blocked;
	rcfg->unblocked_cb = wpa_driver_wext_rfkill_unblocked;
	drv->rfkill = rfkill_init(rcfg);
	if (drv->rfkill == NULL) {
		wpa_printf(MSG_DEBUG, "WEXT: RFKILL status not available");
		os_free(rcfg);
	}

	drv->mlme_sock = -1;
#ifdef ANDROID
	drv->errors = 0;
	drv->driver_is_started = TRUE;
	drv->skip_disconnect = 0;
	drv->bgscan_enabled = 0;
#endif

	if (wpa_driver_wext_finish_drv_init(drv) < 0)
		goto err3;

	wpa_driver_wext_set_auth_param(drv, IW_AUTH_WPA_ENABLED, 1);

	return drv;

err3:
	rfkill_deinit(drv->rfkill);
	netlink_deinit(drv->netlink);
err2:
	close(drv->ioctl_sock);
err1:
	os_free(drv);
	return NULL;
}

其中参数ifname在/data/misc/wifi/wpa_supplicant.conf中被定义,如我的如下:
update_config=1
ctrl_interface=wlan0
eapol_version=1
ap_scan=1
fast_reauth=1


2.3.1 ioctl实现方案

      在用户态可简单执行一个ioctl(fd,cmd,...)命令即可。

      在Kernel态则是通过唯一的cmd (SIOCIWFIRST--SIOCIWLAST) 来进行区分,从而执行cfg80211_handlers中对应的函数。

      socket文件操作如下:

/*
 *	Socket files have a set of 'special' operations as well as the generic file ones. These don't appear
 *	in the operation structures but are done directly via the socketcall() multiplexor.
 */

static const struct file_operations socket_file_ops = {
	.owner =	THIS_MODULE,
	.llseek =	no_llseek,
	.aio_read =	sock_aio_read,
	.aio_write =	sock_aio_write,
	.poll =		sock_poll,
	.unlocked_ioctl = sock_ioctl, // 这个就是被执行的ioctl
#ifdef CONFIG_COMPAT
	.compat_ioctl = compat_sock_ioctl,
#endif
	.mmap =		sock_mmap,
	.open =		sock_no_open,	/* special open code to disallow open via /proc */
	.release =	sock_close,
	.fasync =	sock_fasync,
	.sendpage =	sock_sendpage,
	.splice_write = generic_splice_sendpage,
	.splice_read =	sock_splice_read,
};

从sock_ioctl到iw_handler的执行注程如下所示:

  sock_ioctl->

    dev_ioctl->

      wext_handle_ioctl-> (把执行结果从kernel态copy到用户态)

        wext_ioctl_dispatch->

          wireless_process_ioctl->

             1) get_handler

             2) ioctl_standard_call (执行cmd指定的iw_handler<cfg80211_handlers中定义的>,并返回结果) 


static long sock_ioctl(struct file *file, unsigned cmd, unsigned long arg)
{
	struct socket *sock;
	struct sock *sk;
	void __user *argp = (void __user *)arg;
	int pid, err;
	struct net *net;

	sock = file->private_data;
	sk = sock->sk;
	net = sock_net(sk);
	if (cmd >= SIOCDEVPRIVATE && cmd <= (SIOCDEVPRIVATE + 15)) {
		err = dev_ioctl(net, cmd, argp);
	} else
#ifdef CONFIG_WEXT_CORE
	if (cmd >= SIOCIWFIRST && cmd <= SIOCIWLAST) {
  		err = dev_ioctl(net, cmd, argp); //执行dev_ioctl
	} else
#endif
        ...
	}
	return err;
}

int dev_ioctl(struct net *net, unsigned int cmd, void __user *arg)
{
	struct ifreq ifr;
	int ret;
	char *colon;


	/*
	 *	See which interface the caller is talking about.
	 */

	switch (cmd) {

	/*
	 *	Unknown or private ioctl.
	 */
	default:
		/* Take care of Wireless Extensions */
		if (cmd >= SIOCIWFIRST && cmd <= SIOCIWLAST)
			return wext_handle_ioctl(net, &ifr, cmd, arg); // 执行wext_handle_ioctl
		return -ENOTTY;
	}
}

int wext_handle_ioctl(struct net *net, struct ifreq *ifr, unsigned int cmd,
		      void __user *arg)
{
	struct iw_request_info info = { .cmd = cmd, .flags = 0 };
	int ret;

	ret = wext_ioctl_dispatch(net, ifr, cmd, &info,  // 执行wext_ioctl_dispatch
				  ioctl_standard_call,
				  ioctl_private_call);
	if (ret >= 0 &&
	    IW_IS_GET(cmd) &&
	    copy_to_user(arg, ifr, sizeof(struct iwreq))) // 把执行结果copy到用户空间
		return -EFAULT;

	return ret;
}

/* entry point from dev ioctl */
static int wext_ioctl_dispatch(struct net *net, struct ifreq *ifr,
			       unsigned int cmd, struct iw_request_info *info,
			       wext_ioctl_func standard,
			       wext_ioctl_func private)
{
	int ret = wext_permission_check(cmd);

	if (ret)
		return ret;

	dev_load(net, ifr->ifr_name);
	rtnl_lock();
	ret = wireless_process_ioctl(net, ifr, cmd, info, standard, private); //执行wireless_process_ioctl

	rtnl_unlock();

	return ret;
}

static iw_handler get_handler(struct net_device *dev, unsigned int cmd)
{
	/* Don't "optimise" the following variable, it will crash */
	unsigned int	index;		/* *MUST* be unsigned */
	const struct iw_handler_def *handlers = NULL;
    //printk("***IDONG_WIFI:%s,cmd=0x%x\n",__FUNCTION__,cmd);
#ifdef CONFIG_CFG80211_WEXT
	if (dev->ieee80211_ptr && dev->ieee80211_ptr->wiphy)
		handlers = dev->ieee80211_ptr->wiphy->wext;
#endif
#ifdef CONFIG_WIRELESS_EXT
	if (dev->wireless_handlers)
		handlers = dev->wireless_handlers;
#endif

	if (!handlers)
		return NULL;

	/* Try as a standard command */
	index = IW_IOCTL_IDX(cmd);
	if (index < handlers->num_standard)
		return handlers->standard[index];

#ifdef CONFIG_WEXT_PRIV
	/* Try as a private command */
	index = cmd - SIOCIWFIRSTPRIV;
	if (index < handlers->num_private)
		return handlers->private[index];
#endif

	/* Not found */
	return NULL;
}

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)
{
	struct iwreq *iwr = (struct iwreq *) ifr;
	struct net_device *dev;
	iw_handler	handler;

	/* Permissions are already checked in dev_ioctl() before calling us.
	 * The copy_to/from_user() of ifr is also dealt with in there */

	/* Make sure the device exist */
	if ((dev = __dev_get_by_name(net, ifr->ifr_name)) == NULL)
		return -ENODEV;

	/* A bunch of special cases, then the generic case...
	 * Note that 'cmd' is already filtered in dev_ioctl() with
	 * (cmd >= SIOCIWFIRST && cmd <= SIOCIWLAST) */
	if (cmd == SIOCGIWSTATS)
		return standard(dev, iwr, cmd, info,
				&iw_handler_get_iwstats);

#ifdef CONFIG_WEXT_PRIV
	if (cmd == SIOCGIWPRIV && dev->wireless_handlers)
		return standard(dev, iwr, cmd, info,
				iw_handler_get_private);
#endif

	/* Basic check */
	if (!netif_device_present(dev))
		return -ENODEV;

	/* New driver API : try to find the handler */
	handler = get_handler(dev, cmd);
	if (handler) {
		/* Standard and private are not the same */
		if (cmd < SIOCIWFIRSTPRIV)
			return standard(dev, iwr, cmd, info, handler); // 去执行对应的iw_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);
	return -EOPNOTSUPP;
}


2.3.2 用户态初始化netlink


struct netlink_data * netlink_init(struct netlink_config *cfg)
{
	struct netlink_data *netlink;
	struct sockaddr_nl local;

	netlink = os_zalloc(sizeof(*netlink));
	if (netlink == NULL)
		return NULL;

	netlink->cfg = cfg;

	netlink->sock = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
	if (netlink->sock < 0) {
		wpa_printf(MSG_ERROR, "netlink: Failed to open netlink "
			   "socket: %s", strerror(errno));
		netlink_deinit(netlink);
		return NULL;
	}

	os_memset(&local, 0, sizeof(local));
	local.nl_family = AF_NETLINK;
	local.nl_groups = RTMGRP_LINK;
	if (bind(netlink->sock, (struct sockaddr *) &local, sizeof(local)) < 0)
	{
		wpa_printf(MSG_ERROR, "netlink: Failed to bind netlink "
			   "socket: %s", strerror(errno));
		netlink_deinit(netlink);
		return NULL;
	}

	eloop_register_read_sock(netlink->sock, netlink_receive, netlink,
				 NULL);

	return netlink;
}

2.3.3 用户态netlink事件接收函数netlink_receive


static void netlink_receive(int sock, void *eloop_ctx, void *sock_ctx)
{
	struct netlink_data *netlink = eloop_ctx;
	char buf[8192];
	int left;
	struct sockaddr_nl from;
	socklen_t fromlen;
	struct nlmsghdr *h;
	int max_events = 10;

try_again:
	fromlen = sizeof(from);
	left = recvfrom(sock, buf, sizeof(buf), MSG_DONTWAIT, //从netlink读取事件
			(struct sockaddr *) &from, &fromlen);
	if (left < 0) {
		if (errno != EINTR && errno != EAGAIN)
			wpa_printf(MSG_INFO, "netlink: recvfrom failed: %s",
				   strerror(errno));
		return;
	}

	h = (struct nlmsghdr *) buf;
	while (NLMSG_OK(h, left)) {
		switch (h->nlmsg_type) {
		case RTM_NEWLINK:
			netlink_receive_link(netlink, netlink->cfg->newlink_cb,
					     h);
			break;
		case RTM_DELLINK:
			netlink_receive_link(netlink, netlink->cfg->dellink_cb,
					     h);
			break;
		}

		h = NLMSG_NEXT(h, left);
	}

	if (left > 0) {
		wpa_printf(MSG_DEBUG, "netlink: %d extra bytes in the end of "
			   "netlink message", left);
	}

	if (--max_events > 0) {
		/*
		 * Try to receive all events in one eloop call in order to
		 * limit race condition on cases where AssocInfo event, Assoc
		 * event, and EAPOL frames are received more or less at the
		 * same time. We want to process the event messages first
		 * before starting EAPOL processing.
		 */
		goto try_again;
	}
}



3. SCAN流程

wpa_supplicant_ctrl_iface_process-> ( buf 内容为SCAN )

  wpa_supplicant_req_scan->

    wpa_supplicant_scan->

      wpa_supplicant_trigger_scan->

        wpa_drv_scan->

          wpa_s->driver->scan2->

            wpa_driver_wext_scan-> (Request the driver to initiate scan)      

              wpa_driver_wext_combo_scan->

                ioctl(drv->ioctl_sock, SIOCSIWPRIV, &iwr)-> (User)

                ...

                cfg80211_wext_setpriv (cmd=CSCAN S)->

                  cfg80211_wext_siwscan->

                   rdev->ops->scan (cfg80211_ops mac80211_config_ops->scan)->

                    ieee80211_scan->

                     ieee80211_request_scan->

                      __ieee80211_start_scan->

                       ieee80211_start_sw_scan->

                         <1> drv_sw_scan_start->

                                  local->ops->sw_scan_start(ieee80211_ops ath9k_htc_ops->sw_scan_start)->

                                    ath9k_htc_sw_scan_start->

                         <2> ieee80211_hw_config-> (set power level at maximum rate for scanning)

                                 drv_config->  

                                   local->ops->config( ieee80211_ops ath9k_htc_ops->config)->  

                                      ath9k_htc_config->

                                        ath9k_htc_setpower(priv,ATH9K_PM_AWAKE)                     

                         <3>  ieee80211_queue_delayed_work(&local->scan_work)->

                                 (注:INIT_DELAYED_WORK(&local->scan_work, ieee80211_scan_work))

                                   ieee80211_scan_work-> (根据local当前状态进行下一步工作,直到工作完成)

                                    __ieee80211_scan_completed-> (所有扫描的工作完成之后,调用此函数)

                                     drv_sw_scan_complete->

                                        local->ops->sw_scan_complete( ieee80211_ops ath9k_htc_ops->sw_scan_complete)

                                          ath9k_htc_sw_scan_complete

                                           

• ieee80211_scan_work函数详细代码如下:                                      

void ieee80211_scan_work(struct work_struct *work)
{
	struct ieee80211_local *local =
		container_of(work, struct ieee80211_local, scan_work.work);
	struct ieee80211_sub_if_data *sdata;
	unsigned long next_delay = 0;
	bool aborted, hw_scan;

	mutex_lock(&local->mtx);

	sdata = local->scan_sdata;

	if (test_and_clear_bit(SCAN_COMPLETED, &local->scanning)) {
		aborted = test_and_clear_bit(SCAN_ABORTED, &local->scanning);
		goto out_complete;
	}

	if (!sdata || !local->scan_req)
		goto out;

	if (local->scan_req && !local->scanning) {
		struct cfg80211_scan_request *req = local->scan_req;
		int rc;

		local->scan_req = NULL;
		local->scan_sdata = NULL;

		rc = __ieee80211_start_scan(sdata, req);
		if (rc) {
			/* need to complete scan in cfg80211 */
			local->scan_req = req;
			aborted = true;
			goto out_complete;
		} else
			goto out;
	}

	/*
	 * Avoid re-scheduling when the sdata is going away.
	 */
	if (!ieee80211_sdata_running(sdata)) {
		aborted = true;
		goto out_complete;
	}

	/*
	 * as long as no delay is required advance immediately
	 * without scheduling a new work
	 */
	do {
		if (!ieee80211_sdata_running(sdata)) {
			aborted = true;
			goto out_complete;
		}

		switch (local->next_scan_state) {
		case SCAN_DECISION:
			/* if no more bands/channels left, complete scan */
			if (local->scan_channel_idx >= local->scan_req->n_channels) {
				aborted = false;
				goto out_complete;
			}
			ieee80211_scan_state_decision(local, &next_delay);
			break;
		case SCAN_SET_CHANNEL:
			ieee80211_scan_state_set_channel(local, &next_delay);
			break;
		case SCAN_SEND_PROBE:
			ieee80211_scan_state_send_probe(local, &next_delay);
			break;
		case SCAN_LEAVE_OPER_CHANNEL:
			ieee80211_scan_state_leave_oper_channel(local, &next_delay);
			break;
		case SCAN_ENTER_OPER_CHANNEL:
			ieee80211_scan_state_enter_oper_channel(local, &next_delay);
			break;
		}
	} while (next_delay == 0);

	ieee80211_queue_delayed_work(&local->hw, &local->scan_work, next_delay);
	goto out;

out_complete:
	hw_scan = test_bit(SCAN_HW_SCANNING, &local->scanning);
	__ieee80211_scan_completed(&local->hw, aborted, hw_scan);
out:
	mutex_unlock(&local->mtx);
}


                                         






<think>我们正在处理一个关于wpa_supplicantkernel及WiFidriver交互时出现的错误:'nl80211:SetscandefaultIEsfailed:ret=-22(Invalidargument)'。错误代码-22(EINVAL)表示传递了无效的参数。根据引用[1],我们知道在Linux内核中,WiFi驱动通过cfg80211接口用户空间程序(如wpa_supplicant)通信。具体到扫描操作,wpa_supplicant会通过nl80211发送扫描请求,最终调用到驱动注册的`cfg80211_ops`结构体中的`.scan`函数。错误信息表明在设置扫描默认信息元素(IEs)时失败。信息元素(IEs)通常用于在扫描请求中携带额外的信息,比如特定于供应商的扩展。可能的原因:1.**IE数据格式不正确**:传递给驱动的IE数据可能不符合规范(比如长度错误、格式错误等)。2.**驱动对IE的支持问题**:驱动可能不支持某些类型的IE,或者对IE的长度有限制。3.**内核wpa_supplicant版本不兼容**:不同版本的内核可能对nl80211消息格式有不同要求。调试步骤:###1.**检查wpa_supplicant日志**查看完整的wpa_supplicant日志,确认错误发生的上下文。启用wpa_supplicant的调试信息:```wpa_supplicant-iwlan0-c/etc/wpa_supplicant.conf-dd```注意日志中在设置扫描默认IEs之前的操作,特别是IE的内容。###2.**分析内核代码**我们需要查看内核中处理扫描请求并设置IE的部分。根据引用[1],扫描请求最终会调用驱动注册的`.scan`函数。在调用驱动之前,内核会进行一些参数检查。在Linux内核中,设置扫描默认IEs的函数可能是`nl80211_set_scan_ies`(或类似函数)。错误发生在设置默认IEs时,因此我们需要关注内核中处理`NL80211_CMD_SET_SCAN_IES`命令的部分。可以搜索内核代码中返回`-EINVAL`(-22)的地方,特别是在处理扫描IEs的函数中。例如,在`net/wireless/nl80211.c`中,函数`nl80211_set_scan_ies`可能会检查IE的有效性:```cstaticintnl80211_set_scan_ies(structcfg80211_registered_device*rdev,structgenl_info*info){//...解析info中的IE数据if(ie_len>IEEE80211_MAX_DATA_LEN){return-EINVAL;}//...其他检查}```另外,在驱动中,当收到扫描请求(`.scan`回调)时,驱动也会检查IE数据。###3.**检查驱动代码**驱动在实现`.scan`操作时,可能会对IE数据进行检查。例如,驱动可能要求IE数据必须是某些特定格式,或者长度不能超过限制。根据引用[1]中的示例,驱动函数`__wlan_hdd_cfg80211_scan`最终被调用。在该函数中,可能会将扫描请求(包括IEs)传递给底层固件。如果固件拒绝IE数据,驱动可能会返回-EINVAL。需要检查驱动中处理扫描请求的函数,特别是解析IE的部分。###4.**检查IE内容**错误可能是由于wpa_supplicant试图设置一个无效的IE。IE通常由三部分组成:ElementID(1字节)、Length(1字节)、Data(可变长度)。如果数据长度Length字段不匹配,或者ElementID不在有效范围内,则会导致错误。在wpa_supplicant中,设置扫描IEs的代码通常在`src/drivers/driver_nl80211_scan.c`文件中,函数`nl80211_set_scan_ies`。可以检查该函数中构建IE数据的过程。例如,wpa_supplicant可能通过以下方式设置默认扫描IEs:```cif(wpa_drv_set_default_scan_ies(wpa_s,ies,ies_len)<0){wpa_printf(MSG_DEBUG,"nl80211:SetscandefaultIEsfailed");}```可以检查传入的`ies`和`ies_len`是否有效。###5.**更新或打补丁**如果问题是由于已知的兼容性问题或bug,可以尝试更新wpa_supplicant、内核或驱动。也可以查找相关补丁。###6.**简化测试**尝试使用最简单的配置运行wpa_supplicant(不设置任何额外的IE),看是否还会出现错误。如果不会,则问题可能出在wpa_supplicant配置的IE上。###7.**调试内核**如果可能,可以在内核中相关函数添加调试打印,查看导致-EINVAL的具体原因。总结:该错误通常是由于传递给内核的扫描默认IEs无效导致,需要从wpa_supplicant和内核/驱动两方面检查IE数据的生成和解析。§§相关问题§§1.如何获取wpa_supplicant的详细调试日志?2.如何分析Linux内核中cfg80211和nl80211的交互?3.在WiFi驱动中,如何处理扫描请求中的信息元素(IEs)?4.常见的导致“Invalidargument”错误的原因有哪些?</think>### 排查 `nl80211: Set scan default IEs failed: ret=-22 (Invalid argument)` 错误 此错误表明 `wpa_supplicant` 在通过 **nl80211 接口**向内核发送扫描默认信息元素(IEs)时,内核或驱动检测到**无效参数**(错误码 -22 = `EINVAL`)。以下是系统级的排查步骤: --- #### 1. **定位错误触发点** - **内核调用栈**:错误发生在 `nl80211` 子系统处理 `NL80211_CMD_SET_SCAN_IES` 命令时。 - **关键函数**: ```c // net/wireless/nl80211.c static int nl80211_set_scan_ies(struct cfg80211_registered_device *rdev, struct genl_info *info) { u8 *ies; size_t ies_len; // 解析用户空间传入的IE数据 if (nla_parse( ... )) // 可能因数据格式错误返回-EINVAL ... err = rdev_set_scan_ies(rdev, ie_attr); // 最终调用驱动注册的cfg80211_ops->set_scan_ies if (err) return err; // 驱动可能返回-EINVAL } ``` - **驱动层**:驱动注册的 `cfg80211_ops->set_scan_ies` 接口可能因以下原因返回 `-EINVAL`: - IE 数据长度超限 - IE 结构不符合协议规范(如 802.11) - 硬件不支持特定 IE 类型 --- #### 2. **检查 wpa_supplicant 的 IE 数据** - **调试日志**:启用 `wpa_supplicant` 的详细日志: ```bash wpa_supplicant -i wlan0 -c /etc/wpa_supplicant.conf -dd -K ``` 观察日志中 `Setting scan default IEs` 后的数据内容: ``` wpa_driver_nl80211_set_scan_ies: ie_len=128 hexdump: 01 04 02 00 00 00 ... [可能包含非法字节] ``` - **常见问题**: - IE 长度字段实际数据不匹配 - 包含硬件不支持的 IE 类型(如特定厂商 IE) - 数据包含未初始化内存(野指针) --- #### 3. **内核驱动层排查** - **驱动代码检查**:在驱动实现的 `set_scan_ies` 函数中查找 `-EINVAL` 返回点: ```c // 驱动代码示例 (如 wlan_hdd_cfg80211.c) static int drv_set_scan_ies(struct wiphy *wiphy, u8 *ies, size_t len) { if (len > MAX_IE_LEN) { // 驱动定义的IE长度上限 hdd_err("IE length %zu exceeds limit", len); return -EINVAL; } if (validate_ie(ies, len) != 0) { // 驱动自定义校验失败 return -EINVAL; } ... } ``` - **内核校验逻辑**: - 检查 `net/wireless/nl80211.c` 中 `nla_parse()` 对 NL80211_ATTR_IE 属性的解析 - 确认是否因 **TLV 格式错误**(如长度字段为0或溢出)触发错误 --- #### 4. **兼容性问题** - **内核版本差异**:不同内核版本对 IE 的校验严格度不同(如 4.19 vs 5.10) - **固件限制**:部分 WiFi 芯片固件对 IE 的支持有限,需检查驱动固件的兼容性: ```bash dmesg | grep firmware # 确认固件加载是否正常 ``` --- #### 5. **修复建议** - **临时规避**:在 `wpa_supplicant` 配置中禁用 IE 设置: ```conf # /etc/wpa_supplicant.conf disable_scan_offload=1 ``` - **代码修复**: 1. 在 `wpa_supplicant` 中检查 IE 生成逻辑(`src/drivers/driver_nl80211_scan.c`) 2. 确保 IE 长度不超过驱动定义的 `wiphy->max_scan_ie_len` 3. 在驱动中放宽 IE 校验(仅限调试) --- ### 根本原因总结 | 层级 | 可能原因 | |---------------------|--------------------------------------------------------------------------| | **wpa_supplicant** | 生成的 IE 数据格式错误或超长 | | **内核 nl80211** | TLV 解析失败或参数检查不通过 | | **WiFi 驱动** | 硬件限制导致特定 IE 被拒绝,或驱动校验过于严格 | | **固件** | 不支持扫描默认 IE 功能 | > 建议从 `wpa_supplicant` 日志入手,确认 IE 数据合法性,再逐步深入驱动层逻辑[^1][^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值