ifplugd A link detection daemon for ethernet devices
api mode:
- ethtool
- mii
- wlan
- iff
- priv
Force a specific link beat detection ioctl() API. Possible values are auto, iff, wlan, ethtool, mii, and priv for automatic detection, interface flag (IFF_RUNNING), wireless extension, SIOCETHTOOL, SIOCGMIIREG resp. SIOCPRIV. Only the first character of the argument is relevant, case insensitive. (default: auto)
base
if ((fd = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
return -1;
创建socket
用于ioctl
ethtool SIOCETHTOOL
Ethtool是Linux下用于查询及设置网卡参数的命令
#include <sys/ioctl.h>
#include <net/if.h>
interface_status_t interface_detect_beat_ethtool(int fd, char *iface) {
struct ifreq ifr;
struct ethtool_value edata;
if (interface_auto_up)
interface_up(fd, iface);
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, iface, sizeof(ifr.ifr_name)-1);
edata.cmd = ETHTOOL_GLINK;
ifr.ifr_data = (caddr_t) &edata;
if (ioctl(fd, SIOCETHTOOL, &ifr) == -1) {
if (interface_do_message)
daemon_log(LOG_ERR, "ETHTOOL_GLINK failed: %s", strerror(errno));
return IFSTATUS_ERR;
}
return edata.data ? IFSTATUS_UP : IFSTATUS_DOWN;
}
对应内核代码linux/net/core/dev.c
中dev_ioctl()
函数,对应的SIOCETHTOOL
,又调用了linux/net/core/ethtool.c
中的实现,而dev_ethtool()
函数,而这个函数最终又会调用相应if
驱动的ethtool_ops
结构体中注册的函数来实现寄存器的操作。当然,不同的PHY
驱动对此有不同的操作,甚至有些PHY
驱动根本没有注册这个结构体,这时,我们就要尝试其他方式。
static struct ethtool_ops synopGMAC_ethtool_ops = {
.get_link = synopGMAC_get_link
};
.
netdev->ethtool_ops = &synopGMAC_ethtool_ops;
include/linux/ethtool.h
中存在 ioctl
列表 ETHTOOL_*
mii
mii-tool(这是Linux下专门设置网卡工作模式的命令)
MII
的全称是Media Independent Interface
,字面意思上就是媒体无关的接口,因此它是独立于具体设备的,仔细想想标准化的东西都是独立于具体设备的。虽然如此,很多以太网卡设备并不支持这些参数的配置,因此当你执行mii-tool的时候,会得到Operation not supported的提示,要不就是没有使用超级用户身份,还有就是若接口编号设置超过eth7时,直接使用mii-tool 不加参数,会出现NO MII xxxxxxxxxx interface 之类的提示,是因为超过了默认值,使用帮助信息可以了解。
interface_status_t interface_detect_beat_mii(int fd, char *iface) {
struct ifreq ifr;
if (interface_auto_up)
interface_up(fd, iface);
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, iface, sizeof(ifr.ifr_name)-1);
if (ioctl(fd, SIOCGMIIPHY, &ifr) == -1) {
if (interface_do_message)
daemon_log(LOG_ERR, "SIOCGMIIPHY failed: %s", strerror(errno));
return IFSTATUS_ERR;
}
((unsigned short*) &ifr.ifr_data)[1] = 1;
if (ioctl(fd, SIOCGMIIREG, &ifr) == -1) {
if (interface_do_message)
daemon_log(LOG_ERR, "SIOCGMIIREG failed: %s", strerror(errno));
return IFSTATUS_ERR;
}
return (((unsigned short*) &ifr.ifr_data)[3] & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
}
对应内核代码linux/net/core/dev.c
中dev_ioctl()
函数,对应的SIOCxMIIxxx
,又调用了dev_ifsioc()
函数,这个函数最终调用PHY
驱动中注册的do_ioctl()
函数来实现PHY寄存器的读写。
例如内核中winbond-840
初始化如下:
static int __devinit w840_probe1 (struct pci_dev *pdev, const struct pci_device_id *ent)
{
struct net_device *dev;
struct netdev_private *np;
static int find_cnt;
int chip_idx = ent->driver_data;
int irq;
int i, option = find_cnt < MAX_UNITS ? options[find_cnt] : 0;
void __iomem *ioaddr;
...
dev = alloc_etherdev(sizeof(*np));
if (!dev)
return -ENOMEM;
SET_NETDEV_DEV(dev, &pdev->dev);
...
dev->open = &netdev_open;
dev->hard_start_xmit = &start_tx;
dev->stop = &netdev_close;
dev->get_stats = &get_stats;
dev->set_multicast_list = &set_rx_mode;
dev->do_ioctl = &netdev_ioctl; //mii
dev->ethtool_ops = &netdev_ethtool_ops; //ethtool
dev->tx_timeout = &tx_timeout;
dev->watchdog_timeo = TX_TIMEOUT;
i = register_netdev(dev);
}
include/linux/sockios.h
中
#define SIOCGMIIPHY 0x8947 /* Get address of MII PHY in use. */
#define SIOCGMIIREG 0x8948 /* Read MII PHY register. */
#define SIOCSMIIREG 0x8949 /* Write MII PHY register. */
wlan
interface_status_t interface_detect_beat_wlan(int fd, char *iface)
{
uint8_t mac[6];
int q;
struct iwreq req;
if (interface_auto_up)
interface_up(fd, iface);
memset(&req, 0, sizeof(req));
strncpy(req.ifr_ifrn.ifrn_name, iface, IFNAMSIZ);
if (ioctl(fd, SIOCGIWAP, &req) < 0) {
if (interface_do_message)
daemon_log(LOG_WARNING, "Failed to get AP address: %s",strerror(errno));
return IFSTATUS_ERR;
}
memcpy(mac, &(req.u.ap_addr.sa_data), ETH_ALEN);
if (!is_assoc_ap(mac))
return IFSTATUS_DOWN;
if ((q = get_wlan_qual_new(fd, iface)) < 0)
if ((q = get_wlan_qual_old(iface)) < 0) {
if (interface_do_message)
daemon_log(LOG_WARNING, "Failed to get wireless link quality.");
return IFSTATUS_ERR;
}
return q > 0 ? IFSTATUS_UP : IFSTATUS_DOWN;
}
从应用到内核再到wifi驱动的流程如下:
应用上
if (ioctl(fd, SIOCGIWAP, &req) < 0) {
if (interface_do_message)
daemon_log(LOG_WARNING, "Failed to get AP address: %s",strerror(errno));
return IFSTATUS_ERR;
}
其中 include/linux/wireless.h
#define SIOCGIWAP 0x8B15
调用流程如下:
sock_ioctl[net/socket.c]
-> dev_ioctl[net/core/dev.c]
-> wext_handle_ioctl[net/wireless/wext.c]
-> wireless_process_ioctl[net/wireless/wext.c]
对wireless_process_ioctl,又跑到了default中,如果有handler,最后到ioctl_standard_call中;没有handler时,使用老的接口(dev->do_ioctl)
/* 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);
else
return private(dev, iwr, cmd, info, handler);
}
/* Old driver API : call driver ioctl handler */
if (dev->do_ioctl)
return dev->do_ioctl(dev, ifr, cmd);
在WIFI驱动中如下:
static const iw_handler rt_handler[] =
{
(iw_handler) NULL, /* SIOCSIWCOMMIT */
(iw_handler) rt_ioctl_giwname, /* SIOCGIWNAME */
(iw_handler) NULL, /* SIOCSIWNWID */
(iw_handler) NULL, /* SIOCGIWNWID */
(iw_handler) rt_ioctl_siwfreq, /* SIOCSIWFREQ */
(iw_handler) rt_ioctl_giwfreq, /* SIOCGIWFREQ */
(iw_handler) rt_ioctl_siwmode, /* SIOCSIWMODE */
(iw_handler) rt_ioctl_giwmode, /* SIOCGIWMODE */
(iw_handler) NULL, /* SIOCSIWSENS */
(iw_handler) NULL, /* SIOCGIWSENS */
(iw_handler) NULL /* not used */, /* SIOCSIWRANGE */
(iw_handler) rt_ioctl_giwrange, /* SIOCGIWRANGE */
(iw_handler) rt_ioctl_giwpriv, /* SIOCSIWPRIV for Android */
(iw_handler) NULL /* kernel code */, /* SIOCGIWPRIV */
(iw_handler) NULL /* not used */, /* SIOCSIWSTATS */
(iw_handler) rt28xx_get_wireless_stats /* kernel code */, /* SIOCGIWSTATS */
(iw_handler) NULL, /* SIOCSIWSPY */
(iw_handler) NULL, /* SIOCGIWSPY */
(iw_handler) NULL, /* SIOCSIWTHRSPY */
(iw_handler) NULL, /* SIOCGIWTHRSPY */
(iw_handler) rt_ioctl_siwap, /* SIOCSIWAP */
(iw_handler) rt_ioctl_giwap, /* SIOCGIWAP */
};
const struct iw_handler_def rt28xx_iw_handler_def =
{
#define N(a) (sizeof (a) / sizeof (a[0]))
.standard = (iw_handler *) rt_handler,
.num_standard = sizeof(rt_handler) / sizeof(iw_handler),
.private = (iw_handler *) rt_priv_handlers,
.num_private = N(rt_priv_handlers),
.private_args = (struct iw_priv_args *) privtab,
.num_private_args = N(privtab),
#if IW_HANDLER_VERSION >= 7
.get_wireless_stats = rt28xx_get_wireless_stats,
#endif
};
int rt28xx_open(VOID *dev)
{
if (OpMode == OPMODE_STA)
net_dev->wireless_handlers = (struct iw_handler_def *) &rt28xx_iw_handler_def;
}
老的接口实现如下:
RtmpPhyNetDevInit()
{
pNetDevHook->ioctl = rt28xx_ioctl;
pNetDevHook->priv_flags = InfId; /*INT_MAIN; */
pNetDevHook->get_stats = RT28xx_get_ether_stats;
}
RtmpOSNetDevAttach()
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,31)
pNetDevOps->ndo_open = pDevOpHook->open;
pNetDevOps->ndo_stop = pDevOpHook->stop;
pNetDevOps->ndo_start_xmit =
(HARD_START_XMIT_FUNC) (pDevOpHook->xmit);
pNetDevOps->ndo_do_ioctl = pDevOpHook->ioctl;
#else
pNetDev->open = pDevOpHook->open;
pNetDev->stop = pDevOpHook->stop;
pNetDev->hard_start_xmit =
(HARD_START_XMIT_FUNC) (pDevOpHook->xmit);
pNetDev->do_ioctl = pDevOpHook->ioctl;
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,18)
pNetDev->ethtool_ops = &RALINK_Ethtool_Ops;
#endif
/* if you don't implement get_stats, just leave the callback function as NULL, a dummy
function will make kernel panic.
*/
if (pDevOpHook->get_stats)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,31)
pNetDevOps->ndo_get_stats = pDevOpHook->get_stats;
#else
pNetDev->get_stats = pDevOpHook->get_stats;
#endif
/* OS specific flags, here we used to indicate if we are virtual interface */
/* pNetDev->priv_flags = pDevOpHook->priv_flags; */
RT_DEV_PRIV_FLAGS_SET(pNetDev, pDevOpHook->priv_flags);
#if (WIRELESS_EXT < 21) && (WIRELESS_EXT >= 12)
/* pNetDev->get_wireless_stats = rt28xx_get_wireless_stats; */
pNetDev->get_wireless_stats = pDevOpHook->get_wstats;
#endif
#ifdef CONFIG_STA_SUPPORT
#if WIRELESS_EXT >= 12
if (OpMode == OPMODE_STA) {
/* pNetDev->wireless_handlers = &rt28xx_iw_handler_def; */
pNetDev->wireless_handlers = pDevOpHook->iw_handler;
}
#endif /*WIRELESS_EXT >= 12 */
#endif /* CONFIG_STA_SUPPORT */
#ifdef CONFIG_APSTA_MIXED_SUPPORT
#if WIRELESS_EXT >= 12
if (OpMode == OPMODE_AP) {
/* pNetDev->wireless_handlers = &rt28xx_ap_iw_handler_def; */
pNetDev->wireless_handlers = pDevOpHook->iw_handler;
}
#endif /*WIRELESS_EXT >= 12 */
#endif /* CONFIG_APSTA_MIXED_SUPPORT */
/* copy the net device mac address to the net_device structure. */
NdisMoveMemory(pNetDev->dev_addr, &pDevOpHook->devAddr[0],
MAC_ADDR_LEN);
rtnl_locked = pDevOpHook->needProtcted;
}
rt2870_probe()
{
net_dev = RtmpPhyNetDevInit(pAd, &netDevHook);
status = RtmpOSNetDevAttach(OpMode, net_dev, &netDevHook);
}
iff
interface_status_t interface_detect_beat_iff(int fd, char *iface) {
struct ifreq ifr;
if (interface_auto_up)
interface_up(fd, iface);
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, iface, sizeof(ifr.ifr_name)-1);
if (ioctl(fd, SIOCGIFFLAGS, &ifr) == -1) {
if (interface_do_message)
daemon_log(LOG_ERR, "SIOCGIFFLAGS failed: %s", strerror(errno));
return IFSTATUS_ERR;
}
return ifr.ifr_flags & IFF_RUNNING ? IFSTATUS_UP : IFSTATUS_DOWN;
}
调ioctl的SIOCGIFFLAGS
实现,dev_ifsioc_locked
函数(net/core/dev.c)中处理:
case SIOCGIFFLAGS: /* Get interface flags */
ifr->ifr_flags = dev_get_flags(dev);
return 0;
unsigned dev_get_flags(const struct net_device *dev)
{
unsigned flags;
flags = (dev->flags & ~(IFF_PROMISC |
IFF_ALLMULTI |
IFF_RUNNING |
IFF_LOWER_UP |
IFF_DORMANT)) |
(dev->gflags & (IFF_PROMISC |
IFF_ALLMULTI));
if (netif_running(dev)) {
if (netif_oper_up(dev))
flags |= IFF_RUNNING;
if (netif_carrier_ok(dev))
flags |= IFF_LOWER_UP;
if (netif_dormant(dev))
flags |= IFF_DORMANT;
}
return flags;
}
没有由网卡驱动去查硬件,而是查现有的状态标志位来实现。
priv
interface_detect_beat_priv
基本上和 mii
一样,调ioctl
的SIOCDEVPRIVATE
和SIOCDEVPRIVATE+1
实现,dev_ifsioc
函数(net/core/dev.c)中处理同方法mii
,有一点不同就是SIOCDEVPRIVATE
到SIOCDEVPRIVATE + 15
是各个网卡自己定义的命令,有的网卡有实现,有的没有。例如e100就没有实现这个ioctl。而rtl8150的usb版本网卡驱动就有实现。
interface_status_t interface_detect_beat_priv(int fd, char *iface) {
struct ifreq ifr;
if (interface_auto_up)
interface_up(fd, iface);
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, iface, sizeof(ifr.ifr_name)-1);
if (ioctl(fd, SIOCDEVPRIVATE, &ifr) == -1) {
if (interface_do_message)
daemon_log(LOG_ERR, "SIOCDEVPRIVATE failed: %s", strerror(errno));
return IFSTATUS_ERR;
}
((unsigned short*) &ifr.ifr_data)[1] = 1;
if (ioctl(fd, SIOCDEVPRIVATE+1, &ifr) == -1) {
if (interface_do_message)
daemon_log(LOG_ERR, "SIOCDEVPRIVATE+1 failed: %s", strerror(errno));
return IFSTATUS_ERR;
}
return (((unsigned short*) &ifr.ifr_data)[3] & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
}
summarize
ethtool
,mii
和 wlan
都是通过查询硬件寄存器,priv
可能不支持,iff
查询状态标志位