0%

how-to-check-network-status

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.cdev_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.cdev_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 一样,调ioctlSIOCDEVPRIVATESIOCDEVPRIVATE+1实现,dev_ifsioc函数(net/core/dev.c)中处理同方法mii,有一点不同就是SIOCDEVPRIVATESIOCDEVPRIVATE + 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

ethtoolmiiwlan 都是通过查询硬件寄存器,priv 可能不支持,iff 查询状态标志位

Ref

  1. 对netlink无法检测到dellink事件和探测网卡是否插网线方法的简单分析
  2. Linux无线模块WEXT简单分析