client模式下对应接口加入桥接出错
client模式下,响应的接口wlan0 加入桥接时出现如下错误:
root@root:~# brctl addif br-lan wlan0
brctl: bridge br-lan: Operation not supported。
查看相应busybox代码(brctl.c),函数为 brctl_main,
strncpy_IFNAMSIZ(ifr.ifr_name, br);
if (key == ARG_addif || key == ARG_delif) { /* addif or delif */
brif = *argv;
ifr.ifr_ifindex = if_nametoindex(brif);
if (!ifr.ifr_ifindex) {
bb_perror_msg_and_die("iface %s", brif);
}
ioctl_or_perror_and_die(fd,
key == ARG_addif ? SIOCBRADDIF : SIOCBRDELIF, &ifr, "bridge %s", br);
goto done_next_argv;
}
得到addif 和 delif 相应的ioctl命令码为 SIOCBRADDIF 和 SIOCBRDELIF。
内核中对应命令字的定义:
include/linux/sockios.h:#define SIOCBRADDIF 0x89a2 /* add interface to bridge,十进制为35234
查找内核中响应的代码, 此处内核版本为3.10.14。
* Perform the SIOCxIFxxx calls, inside rtnl_lock()
*/
static int dev_ifsioc(struct net *net, struct ifreq *ifr, unsigned int cmd)
{
switch (cmd) {
/*
* Unknown or private ioctl
*/
default:
if ((cmd >= SIOCDEVPRIVATE &&
cmd <= SIOCDEVPRIVATE + 15) ||
cmd == SIOCBONDENSLAVE ||
cmd == SIOCBONDRELEASE ||
cmd == SIOCBONDSETHWADDR ||
cmd == SIOCBONDSLAVEINFOQUERY ||
cmd == SIOCBONDINFOQUERY ||
cmd == SIOCBONDCHANGEACTIVE ||
cmd == SIOCGMIIPHY ||
cmd == SIOCGMIIREG ||
cmd == SIOCSMIIREG ||
cmd == SIOCBRADDIF || // 添加桥接接口
cmd == SIOCBRDELIF || // 删除桥接接口
cmd == SIOCSHWTSTAMP ||
cmd == SIOCWANDEV) {
err = -EOPNOTSUPP;
if (ops->ndo_do_ioctl) {
if (netif_device_present(dev))
err = ops->ndo_do_ioctl(dev, ifr, cmd); // 调用回调函数
else
err = -ENODEV;
}
} else
err = -EINVAL;
}
}
对应的回调函数位于 /net/bridge/br_device.c中
static const struct net_device_ops br_netdev_ops = {
.ndo_open = br_dev_open,
.ndo_stop = br_dev_stop,
.ndo_init = br_dev_init,
.ndo_start_xmit = br_dev_xmit,
.ndo_get_stats64 = br_get_stats64,
.ndo_set_mac_address = br_set_mac_address,
.ndo_set_rx_mode = br_dev_set_multicast_list,
.ndo_change_mtu = br_change_mtu,
.ndo_do_ioctl = br_dev_ioctl, // 此处对应增加(addif)和删除(delif)的回调
#ifdef CONFIG_NET_POLL_CONTROLLER
.ndo_netpoll_setup = br_netpoll_setup,
.ndo_netpoll_cleanup = br_netpoll_cleanup,
.ndo_poll_controller = br_poll_controller,
#endif
.ndo_add_slave = br_add_slave,
.ndo_del_slave = br_del_slave,
.ndo_fix_features = br_fix_features,
.ndo_fdb_add = br_fdb_add,
.ndo_fdb_del = br_fdb_delete,
.ndo_fdb_dump = br_fdb_dump,
.ndo_bridge_getlink = br_getlink,
.ndo_bridge_setlink = br_setlink,
.ndo_bridge_dellink = br_dellink,
};
查看回调函数如下:
int br_dev_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
{
struct net_bridge *br = netdev_priv(dev);
switch(cmd) {
// 旧式的处理方式
case SIOCDEVPRIVATE:
return old_dev_ioctl(dev, rq, cmd);
// 实现如下:
case SIOCBRADDIF:
case SIOCBRDELIF:
return add_del_if(br, rq->ifr_ifindex, cmd == SIOCBRADDIF);
}
br_debug(br, "Bridge does not support ioctl 0x%x\n", cmd);
return -EOPNOTSUPP;
}
位于 /net/bridge/br_ioctl.c中
static int add_del_if(struct net_bridge *br, int ifindex, int isadd)
{
struct net *net = dev_net(br->dev);
struct net_device *dev;
int ret;
if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
return -EPERM;
dev = __dev_get_by_index(net, ifindex);
if (dev == NULL)
return -EINVAL;
if (isadd)
ret = br_add_if(br, dev); // 添加桥接接口
else
ret = br_del_if(br, dev); // 删除桥接接口
return ret;
}
位于 /net/bridge/br_if.c中
int br_add_if(struct net_bridge *br, struct net_device *dev)
{
struct net_bridge_port *p;
int err = 0;
bool changed_addr;
/* Don't allow bridging non-ethernet like devices */
if ((dev->flags & IFF_LOOPBACK) ||
dev->type != ARPHRD_ETHER || dev->addr_len != ETH_ALEN ||
!is_valid_ether_addr(dev->dev_addr))
return -EINVAL;
/* No bridging of bridges */
if (dev->netdev_ops->ndo_start_xmit == br_dev_xmit)
return -ELOOP;
/* Device is already being bridged */
if (br_port_exists(dev))
return -EBUSY;
/* No bridging devices that dislike that (e.g. wireless) */
if (dev->priv_flags & IFF_DONT_BRIDGE) // wlan0加入桥接时在此处遇到问题,退出
return -EOPNOTSUPP;
p = new_nbp(br, dev);
if (IS_ERR(p))
return PTR_ERR(p);
call_netdevice_notifiers(NETDEV_JOIN, dev);
err = dev_set_promiscuity(dev, 1);
if (err)
goto put_back;
err = kobject_init_and_add(&p->kobj, &brport_ktype, &(dev->dev.kobj),
SYSFS_BRIDGE_PORT_ATTR);
if (err)
goto err1;
err = br_sysfs_addif(p);
if (err)
goto err2;
if (br_netpoll_info(br) && ((err = br_netpoll_enable(p, GFP_KERNEL))))
goto err3;
err = netdev_master_upper_dev_link(dev, br->dev);
if (err)
goto err4;
err = netdev_rx_handler_register(dev, br_handle_frame, p);
if (err)
goto err5;
dev->priv_flags |= IFF_BRIDGE_PORT;
dev_disable_lro(dev);
list_add_rcu(&p->list, &br->port_list);
netdev_update_features(br->dev);
spin_lock_bh(&br->lock);
changed_addr = br_stp_recalculate_bridge_id(br);
if (netif_running(dev) && netif_oper_up(dev) &&
(br->dev->flags & IFF_UP))
br_stp_enable_port(p);
spin_unlock_bh(&br->lock);
br_ifinfo_notify(RTM_NEWLINK, p);
if (changed_addr)
call_netdevice_notifiers(NETDEV_CHANGEADDR, br->dev);
dev_set_mtu(br->dev, br_min_mtu(br));
if (br_fdb_insert(br, p, dev->dev_addr, 0))
netdev_err(dev, "failed insert local address bridge forwarding table\n");
kobject_uevent(&p->kobj, KOBJ_ADD);
return 0;
err5:
netdev_upper_dev_unlink(dev, br->dev);
err4:
br_netpoll_disable(p);
err3:
sysfs_remove_link(br->ifobj, p->dev->name);
err2:
kobject_put(&p->kobj);
p = NULL; /* kobject_put frees */
err1:
dev_set_promiscuity(dev, -1);
put_back:
dev_put(dev);
kfree(p);
return err;
}
追溯出问题的地方,查找 IFF_DONT_BRIDGE 标志置位的地方,找到如下:
static int cfg80211_netdev_notifier_call(struct notifier_block *nb,
unsigned long state, void *ndev)
{
.......
switch (state) {
case NETDEV_REGISTER: // 注册设备
if ((wdev->iftype == NL80211_IFTYPE_STATION ||
wdev->iftype == NL80211_IFTYPE_P2P_CLIENT ||
wdev->iftype == NL80211_IFTYPE_ADHOC) && !wdev->use_4addr)
dev->priv_flags |= IFF_DONT_BRIDGE;
break;
}
.........
}
从以上红色部分可以看出,当设备为client或者adhoc以及wds时,对应的无线接口是无法加入到桥接中去的。
在br_add_if 中判断标志位出错后,返回值为EOPNOTSUPP,定义如下:
#define EOPNOTSUPP 45 /* Op not supported on transport endpoint */
跟串口中配置出错时打印相符。