DHCP应用——获取下挂设备列表信息

大部分路由器,CPE都会显示下挂设备列表,即lan侧设备。通常做法是通过读取arp表和DHCP租期文件,以获取lan侧设备的ip,mac,hostname等信息。但这种方式有两个弊端:
1 . arp表有老化时间,意味着arp表不是实时更新的,即可能出现设备已经断开,但arp表还有其IP和MAC的对应信息。导致显示错误。
2 . 通过读取DHCP租期文件获取数据,当设备为桥接模式时,会获取不到任何数据。

解决方法

arp老化时间

此问题可通过arping解决,从arp获取到lan侧设备IP,先通过arping(有的设备会禁ping,使用ping结果不一定准确)测试其是否实时连接,若arping不通,则说明此设备已断开连接。lan设备列表则不显示此IP。

桥接模式

桥接模式下获取不到数据是因为本地DHCP已关闭,LAN侧设备的IP是通过上级路由分配的,本地的DHCP租期文件无任何数据。
由于LAN侧设备与上级路由进行DHCP交互的时候,DHCP包会经过路由器LAN口,所以可通过在LAN口抓取DHCP包,解包获取其IP,MAC等信息。

具体实现

DHCP包格式详情DHCP包格式
整体思路是添加内核模块,在网口侧抓DHCP Request包,再逐层解析数据包,最后从option中,解析出IP等信息。其中option 12是hostname,option 60是vendor,option 50是requested ip。
解包接口如下:

unsigned int apModeListDhcpHook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
{
        unsigned int isGetIP = 0;
        unsigned int isGetHostname = 0;
        unsigned int isGetVendor = 0;
        unsigned int unUdpLen = 0;
        unsigned int currentIndex = 0;
        unsigned int optionLen = 0;
        unsigned char mac[MAX_MAC_SIZE] = "";
        unsigned char hostname[MAX_STR_SIZE] = "";
        unsigned char vendor[MAX_STR_SIZE] = "";
        unsigned char ip[MAX_IP_SIZE] = "";
        const unsigned char *ucUdpData = NULL;
        const struct iphdr *iph = NULL;
        const struct udphdr *uh = NULL;

        // 没有in设备
        if (unlikely(!state->in))
        {
                return NF_ACCEPT;
        }

        if (__constant_htons(ETH_P_IP) == skb->protocol)
        {
                iph = ip_hdr(skb);
                if (unlikely(!iph))
                {
                        return NF_ACCEPT;
                }
                // 非UDP协议
                if (iph->protocol != IPPROTO_UDP)
                {
                        return NF_ACCEPT;
                }
                // 非67端口
                uh = (struct udphdr *) ((unsigned char *) iph + (iph->ihl << 2));
                if(__constant_ntohs(67) != uh->dest)
                {
                        return NF_ACCEPT;
                }
                // UDP数据开始部分
                ucUdpData = (unsigned char *)((unsigned char *)uh + 8);
                unUdpLen = ntohs(uh->len);

                if (unUdpLen < DHCP_TYPE_OFFSET)
                {
                        return NF_ACCEPT;
                }
                // 不是request包
                if (ucUdpData[DHCP_TYPE_OFFSET] != DHCP_TYPE_REQUEST)
                {
                        return NF_ACCEPT;
                }
                // 拷贝mac
                if (0 == memcmp(ucUdpData + DHCP_CLIENT_MAC_OFFSET, FFMAC, MAX_MAC_SIZE))
                {
                	return NF_ACCEPT;
                }
                memset(mac, 0, MAX_MAC_SIZE);
                memcpy(mac, ucUdpData + DHCP_CLIENT_MAC_OFFSET, MAX_MAC_SIZE);

                hostnameLen = 0;
                vendorLen   = 0;
                memset(hostname, 0, MAX_STR_SIZE);
                memset(vendor,   0, MAX_STR_SIZE);
                memset(ip,       0, MAX_IP_SIZE);

                // 开始遍历option, option是tlv结构,option 12 为 hostname, option 60 为 vendor, option 50 为 requested ip
                currentIndex = DHCP_TYPE_OFFSET + 1;
                while (currentIndex < unUdpLen)
                {
                        if (12 == ucUdpData[currentIndex]) // option 12, can get hostname
                        {
                                optionLen = ucUdpData[++currentIndex];
                                hostnameLen = optionLen > MAX_STR_SIZE ? MAX_STR_SIZE : optionLen;
                                memcpy(hostname, ucUdpData + currentIndex + 1, hostnameLen);
                                isGetHostname = 1;
                        }
                        else if (50 == ucUdpData[currentIndex]) // option 50, can get client ip
                        {
                                optionLen = ucUdpData[++currentIndex];
                                memcpy(ip, ucUdpData + currentIndex + 1, optionLen > MAX_IP_SIZE ? MAX_IP_SIZE : optionLen);
                                isGetIP = 1;
                        }
                        else if (60 == ucUdpData[currentIndex]) // option 60, can get vendor
                        {
                                optionLen = ucUdpData[++currentIndex];
                                vendorLen = optionLen > MAX_STR_SIZE ? MAX_STR_SIZE : optionLen;
                                memcpy(vendor, ucUdpData + currentIndex + 1, vendorLen);
                                isGetVendor = 1;
                        }
                        else if (255 == ucUdpData[currentIndex]) // option 255, end
                        {
                                break;
                        }
                        else // 其他数据,偏移指针继续遍历
                        {
                                optionLen = ucUdpData[++currentIndex];
                        }
                        currentIndex += (optionLen + 1);

                        // ip 、hostname 、vendor都获取到了,提前退出
                        if (3 == isGetHostname + isGetIP + isGetVendor)
                        {
                                break;
                        }
                }

				// 有的设备没有字段,加上默认值
                if (isGetHostname != 1)
                {
                        hostnameLen = strlen("unknow-host");
                        memcpy(hostname, "unknow-host", hostnameLen);
                }
                if (isGetVendor != 1)
                {
                        vendorLen = strlen("no-vendor");
                        memcpy(vendor, "no-vendor", vendorLen);
                }

                add_client_info_to_list(mac, hostname, vendor, ip);
        }

        return NF_ACCEPT;
}

执行结果如下:
在这里插入图片描述

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值