文章目录
需求
局域网设备自动发现是软件开发中的一个常见且重要的需求,它简化了设备间的协作机制,降低了软件各模块间进行复杂配置的需求。通过实现自动发现功能,不仅显著提升了用户的操作便捷性和满意度,还促进了网络资源的智能化分配与高效利用。为后续的通信和传输奠定了基础。
局域网设备自动发现通常具有以下几个核心功能:
- 实时性:能够即时探测到局域网中新加入或离开的设备,确保网络环境的实时更新。
- 广泛性:支持多种类型的设备发现,包括但不限于计算机、打印机、智能家电、网络摄像头等。
- 易用性:用户无需手动配置或输入设备的IP地址等信息,即可轻松访问和使用这些设备。
- 安全性:在自动发现设备的同时,也考虑到了网络安全问题,如防止未授权设备的接入等。
实现方法
ARP (Address Resolution Protocol)
ARP是一种协议,用于将 IP地址解析成 MAC 地址。当主机想要与同一局域网内的另一台主机通信时,它需要知道目标主机的 MAC 地址。ARP 就是用来完成这一任务的协议。ARP 请求是通过广播方式进行的,所有接收到 ARP 请求的设备都会检查是否请求的是自己的 IP 地址,如果是,则响应自己的 MAC 地址。
Ping ip的流程
当你使用 Ping 向某个 IP 地址发送数据包时,首先需要知道该 IP 地址对应的 MAC 地址。这就是 ARP 的作用
一台计算机 A 要 Ping 另一台计算机 B,过程如下:
- A 查询自己的 ARP 缓存表,看是否有 B 的 IP 地址对应的 MAC 地址记录。
- 如果找不到,则 A 发送一个 ARP 请求到局域网内所有设备(广播),询问谁拥有 B 的 IP 地址。
- B 接收到 ARP 请求后,如果匹配自己的 IP 地址,就回复 ARP 响应给 A,告诉它自己的 MAC 地址。
- A 收到 ARP 响应后,记录下 B 的 MAC 地址,并更新 ARP 缓存表。
- A 然后构造一个 ICMP Echo Request 数据包,并使用 B 的 MAC 地址发送给 B。
- B 收到 ICMP Echo Request 后,回应一个 ICMP Echo Reply 给 A。
- A 收到回声应答,表示连通性测试成功。
抓包如下
代码实现
下面是简单c语言示例,
使用原始套接字发送 ARP 请求并接收 ARP 响应来获取局域网内所有在线设备的 IP 地址
安装 npm install libpcap-dev
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/if_ether.h>
#include <netinet/ip.h>
#include <netinet/arp.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <ifaddrs.h>
#define BROADCAST_MAC "\xff\xff\xff\xff\xff\xff"
void send_arp_request(int sockfd, struct ether_header *eh, struct arphdr *arp, char *mac, char *ip, char *target_ip) {
memset(arp, 0, sizeof(struct arphdr));
arp->ar_hrd = htons(ARPHRD_ETHER); /* Ethernet */
arp->ar_pro = htons(ETH_P_IP); /* IP Protocol */
arp->ar_op = htons(AROP_REQUEST); /* ARP Request */
memcpy(arp->ar_saddr, mac, ETH_ALEN); /* Sender hardware address */
memcpy(arp->ar_sha, mac, ETH_ALEN); /* Sender hardware address */
memcpy(arp->ar_tpa, inet_aton(target_ip), 4); /* Target protocol address */
memcpy(arp->ar_tha, "\x00\x00\x00\x00\x00\x00", 6); /* Target hardware address (all zeros) */
memcpy(arp->ar_spa, inet_aton(ip), 4); /* Sender protocol address */
eh->ether_dhost[0] = BROADCAST_MAC[0]; /* Destination MAC address (Broadcast) */
eh->ether_dhost[1] = BROADCAST_MAC[1];
eh->ether_dhost[2] = BROADCAST_MAC[2];
eh->ether_dhost[3] = BROADCAST_MAC[3];
eh->ether_dhost[4] = BROADCAST_MAC[4];
eh->ether_dhost[5] = BROADCAST_MAC[5];
eh->ether_type = htons(ETHER_TYPE_ARP); /* ARP Packet */
sendto(sockfd, eh, sizeof(struct ether_header) + sizeof(struct arphdr), 0, NULL, 0);
}
int main(void) {
struct ifaddrs *ifAddrStruct = NULL;
struct ifaddrs *tmpAddrPtr = NULL;
int sockfd;
char *target_ip = "192.168.1.0"; /* 目标 IP 地址段 */
int i = 0;
int len = 0;
/* 获取本地接口信息 */
if (getifaddrs(&ifAddrStruct) == -1) {
perror("getifaddrs");
return -1;
}
/* 打开原始套接字 */
if ((sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) == -1) {
perror("socket");
freeifaddrs(ifAddrStruct);
return -1;
}
tmpAddrPtr = ifAddrStruct;
while (tmpAddrPtr != NULL) {
if (tmpAddrPtr->ifa_addr != NULL && strcmp(tmpAddrPtr->ifa_name, "lo") != 0) {
char *ip = inet_ntoa(*(struct in_addr *)tmpAddrPtr->ifa_addr);
struct ether_header eh;
struct arphdr arp;
send_arp_request(sockfd, &eh, &arp, (char *)tmpAddrPtr->ifa_ifu.ifu_data, ip, target_ip);
/* 循环发送 ARP 请求 */
for (i = 1; i <= 254; i++) {
char ipaddr[INET_ADDRSTRLEN];
sprintf(ipaddr, "%s.%d", target_ip, i);
send_arp_request(sockfd, &eh, &arp, (char *)tmpAddrPtr->ifa_ifu.ifu_data, ip, ipaddr);
}
}
tmpAddrPtr = tmpAddrPtr->ifa_next;
}
/* 清理 */
freeifaddrs(ifAddrStruct);
close(sockfd);
return 0;
}
mDNS
- mDNS(Multicast DNS)协议:使用5353端口,组播地址 224.0.0.251。
在一个没有常规DNS服务器的小型网络内,可以使用mDNS来实现类似DNS的编程接口、包格式和操作语义。MDNS协议的报文与DNS的报文结构相同,但有些字段对于MDNS来说有新的含义。
在局域网中,设备和设备之前相互通信需要知道对方的ip地址的,大多数情况,设备的ip不是静态ip地址,而是通过dhcp 协议动态分配的ip 地址,mDNS如何设备发现呢,
- UPnP(Universal Plug and Play)技术:UPnP技术旨在让智能设备能够自动发现网络上的其他UPnP设备,并与之进行通信和协作。它支持设备之间的动态服务发现、自动配置和事件通知等功能。
- LLMNR(Link-Local Multicast Name Resolution)协议:作为DNS的一种补充,LLMNR协议在局域网内通过多播方式解析设备的名称和地址。它特别适用于IPv6网络环境,并能够在DNS服务不可用的情况下提供快速的名称解析服务。
- SNMP(Simple Network Management Protocol)协议:虽然SNMP主要用于网络管理而非设备发现,但它可以通过轮询网络中的设备来收集其状态信息,从而间接实现设备发现的目的。通过SNMP,管理员可以获取设备的型号、序列号、固件版本等详细信息。
- 自定义协议
对比测试
Avahi 介绍
Avahi 是一个开源项目,提供了一套用于实现 mDNS (Multicast DNS) 和 SSDP (Simple Service
Discovery Protocol) 的工具和库。它主要用于局域网内的零配置网络服务发现。Avahi 支持多种操作系统,包括 Linux、BSD 变体以及其他类 Unix 系统。
Avahi 库的主要用途
- 服务发现: 让设备在本地网络中自动发现彼此提供的服务。
- 名称解析: 自动解析设备和服务的名称到 IP 地址,而无需手动配置。
- 广告服务: 让设备能够发布自己提供的服务,以便其他设备可以发现。
- 多播 DNS: 利用 mDNS 进行服务发现和名称解析。
- 简单服务发现协议: 通过 SSDP 进行服务发现
Avahi 安装
yum install avahi libavahi-client-devel
Avahi 使用
常用API
初始化
avahi_init();
创建 Avahi 客户端:
AvahiClient *client;
client = avahi_client_new(avahi_poll_get(), AVAHI_CLIENT_FLAG_USE_MULTICAST, NULL, NULL);
回调监听状态变化
static void client_callback(AvahiClient *client, AvahiClientState state, void *userdata) {
// 处理状态变化
}
avahi_client_set_callback(client, client_callback, NULL);
测试代码
#include <avahi-common/poll.h>
#include <avahi-core/core.h>
#include <avahi-client/client.h>
#include <stdio.h>
#include <stdlib.h>
static void service_resolved(AvahiServiceResolvedEvent *event, void *userdata) {
printf("Found service '%s' at '%s', port %d.\n",
event->name, event->address, event->port);
}
static void client_callback(AvahiClient *client, AvahiClientState state, void *userdata) {
AvahiEntryGroup *group;
AvahiEntryGroupState group_state;
AvahiServiceResolver *res;
if (state != AVAHI_CLIENT_S_RUNNING)
return;
/* Create a new entry group */
if ((group = avahi_client_new_entry_group(client)) == NULL) {
fprintf(stderr, "No memory, aborting.\n");
return;
}
/* Setup callback for the entry group */
avahi_entry_group_set_callback(group, entry_group_state_callback, userdata);
/* Add a service browser to the entry group */
if (avahi_entry_group_add_service_browser(group,
AVAHI_IF_UNSPECIFIED,
AVAHI_PROTO_INET,
"_service._tcp", NULL, NULL) < 0) {
fprintf(stderr, "Could not add service browser to entry group.\n");
avahi_entry_group_free(group);
return;
}
/* Commit the entry group */
if (avahi_entry_group_commit(group) < 0) {
fprintf(stderr, "Could not commit entry group.\n");
avahi_entry_group_free(group);
return;
}
/* Wait until the entry group is ready */
do {
avahi_entry_group_get_state(group, &group_state);
} while (group_state != AVAHI_ENTRY_GROUP_COMMITTED);
/* Now we can resolve services */
if ((res = avahi_client_alloc_service_resolver(client)) == NULL) {
fprintf(stderr, "No memory, aborting.\n");
return;
}
avahi_service_resolver_set_callback(res, service_resolved, NULL);
/* Resolve the first found service */
avahi_service_resolver_resolve(res,
AVAHI_IF_UNSPECIFIED,
AVAHI_PROTO_INET,
"my-service", "local", NULL);
}
int main(int argc, char *argv[]) {
AvahiClient *client;
AvahiClientState initial_state;
/* Initialize Avahi library */
avahi_init();
/* Create a new client object */
if ((client = avahi_client_new(avahi_poll_get(), AVAHI_CLIENT_FLAG_USE_MULTICAST, client_callback, NULL)) == NULL) {
fprintf(stderr, "Failed to create client.\n");
return 1;
}
/* Get the initial client state */
avahi_client_get_state(client, &initial_state);
/* Main loop */
while (initial_state != AVAHI_CLIENT_S_RUNNING) {
avahi_client_wait(client, 1000);
avahi_client_get_state(client, &initial_state);
}
/* Clean up */
avahi_client_free(client);
return 0;
}