最近琢磨设备发现的事,尝试了UDP广播。结论不尽如人意。
目录
UDP广播是数据报群发方式,分两种:“255.255.255.255”的物理群发,同一个物理连接的都能收到、网关不转发,“x.x.x.255”的网段群发,整个网段都能收到、网关会转发。
注意,“广播”不是“多播”,多播是一组特殊的IP地址,只有明确加入到一个组的设备才能收到,感觉是为电视直播这种应用准备的。
广播可以用来同时控制一个交换机上的所有设备或者一个网段的所有设备,也可以用来发现设备,我想用的就是发现设备。
我设想用网线直接连接设备和PC,然后发送目标为“255.255.255.255”的广播,这样就可以发现未知IP的设备,从UDP的源信息就可以知道设备的IP地址,或者在数据包里携带网络配置信息。只要能进行数据交换,后面的就好说了。
代码是网上搜的,稍微加工了一下。分为接收和发送两个函数,分别在两个线程里运行。
接收的代码:
- int sockfd;
- struct sockaddr_in saddr;
- int r;
- char recvline[1025];
- struct sockaddr_in presaddr;
- socklen_t len;
-
- sockfd = socket(AF_INET, SOCK_DGRAM, 0);
- bzero(&saddr, sizeof(saddr));
- saddr.sin_family = AF_INET;
- saddr.sin_addr.s_addr = htonl(INADDR_ANY);
- saddr.sin_port = htons(9999);
- bind(sockfd, (struct sockaddr*)&saddr, sizeof(saddr));
- while (1)
- {
- r = recvfrom(sockfd, recvline, sizeof(recvline), 0, (struct sockaddr*)&presaddr, &len);
- if (r <= 0)
- {
- perror("");
- exit(-1);
- }
- recvline[r] = 0;
- cout << "recvfrom " << inet_ntoa(presaddr.sin_addr) << " " << recvline << endl;
- }
使用9999端口,循环接收数据,输出源IP和收到的信息。
发送的代码:
- int sockfd;
- struct sockaddr_in des_addr;
- int r;
- const int on = 1;
-
- sockfd = socket(AF_INET, SOCK_DGRAM, 0);
- setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)); //设置套接字选项
- bzero(&des_addr, sizeof(des_addr));
- des_addr.sin_family = AF_INET;
- string to = "255.255.255.255";
- des_addr.sin_addr.s_addr = inet_addr("255.255.255.255"); //广播地址,这个可能只发送给一个网卡(没有收到,不确定原因)
- des_addr.sin_port = htons(9999);
- string msg = "物理广播 " + to;
- r = sendto(sockfd, msg.c_str(), msg.size(), 0, (struct sockaddr*)&des_addr, sizeof(des_addr));
- if (r <= 0)
- {
- perror("");
- exit(-1);
- }
- cout << "finish" << endl;
-
-
- struct ifaddrs* ifc, * ifc1;
- char ip[64] = {};
- char nm[64] = {};
-
- if (0 != getifaddrs(&ifc))
- {
- thelog << "getifaddrs error" << ende;
- return;
- }
- ifc1 = ifc;
-
- printf("iface\tIP address\tNetmask\n");
- for (; NULL != ifc; ifc = (*ifc).ifa_next)
- {
- printf("%s", (*ifc).ifa_name);
- if (NULL != (*ifc).ifa_addr)
- {
- inet_ntop(AF_INET, &(((struct sockaddr_in*)((*ifc).ifa_addr))->sin_addr), ip, 64);
- printf("\t%s", ip);
- }
- else
- {
- printf("\t\t");
- }
- if (NULL != (*ifc).ifa_netmask)
- {
- inet_ntop(AF_INET, &(((struct sockaddr_in*)((*ifc).ifa_netmask))->sin_addr), nm, 64);
- printf("\t%s", nm);
- }
- else
- {
- printf("\t\t");
- }
- printf("\n");
-
- if (NULL != (*ifc).ifa_netmask)
- {
- //向特定IP段发送
- unsigned int a = *(unsigned int*)&(((struct sockaddr_in*)((*ifc).ifa_addr))->sin_addr);
- unsigned int b = *(unsigned int*)&(((struct sockaddr_in*)((*ifc).ifa_netmask))->sin_addr);
- b = ~b;
- a = a | b;
- des_addr.sin_addr = *(in_addr*)&a;
- inet_ntop(AF_INET, &des_addr.sin_addr, nm, 64);
- printf("发送给 \t%s\n", nm);
- msg = "网段广播 ";msg +=nm;
- r = sendto(sockfd, msg.c_str(), msg.size(), 0, (struct sockaddr*)&des_addr, sizeof(des_addr));
- if (r <= 0)
- {
- perror("");
- //exit(-1);
- }
- }
-
- }
-
- //freeifaddrs(ifc);
- freeifaddrs(ifc1);
这个代码看起来有点长,是因为我把遍历网络接口的代码嵌进来了。代码中首先发送给“255.255.255.255”,然后遍历网络接口,发送给每个网段。
广播的代码,由于未知的原因,不能向“255.255.255.255”广播,引发异常,用管理员身份运行也不行,也尝试了C++代码(上面的代码稍加修改就可以在windows上运行),仍然不行。所以只能使用网段广播。
- Socket roadcast_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
- roadcast_socket.Bind(new IPEndPoint(IPAddress.Any, 9998));
- Thread tBroadcast = new Thread(() =>
- {
- EndPoint topoint = new IPEndPoint(IPAddress.Parse("192.168.136.255"), 9999);//地址255.255.255.255始终是失败的
- while (true)
- {
- try
- {
- roadcast_socket.SendTo(Encoding.UTF8.GetBytes("从PC广播===================================="), topoint);
- }
- catch (Exception ex) { MessageBox.Show(ex.ToString()); break; }
- Thread.Sleep(1000);
- }
- });
- tBroadcast.IsBackground = true;
- tBroadcast.Start();
接收的代码:
- Socket udp_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
- udp_socket.Bind(new IPEndPoint(IPAddress.Any, 9999));
- Thread tRecvt = new Thread(() =>
- {
- while (true)
- {
- try
- {
- byte[] buf = new byte[1024];
- EndPoint frompoint = new IPEndPoint(IPAddress.Any, 0);
- int length = udp_socket.ReceiveFrom(buf, ref frompoint);
- string message = Encoding.UTF8.GetString(buf, 0, length);
- //MessageBox.Show(message, frompoint.ToString());
- CallShowMesssage.Invoke(message + " from " + frompoint.ToString());
- if (null != frompoint.ToString())
- {
- string[] strings = frompoint.ToString().Split(":");
- IPEndPoint respondTo = new IPEndPoint(IPAddress.Parse(strings[0]), 9999);
- CallShowMesssage.Invoke("respondTo " + respondTo.ToString());
- udp_socket.SendTo(Encoding.UTF8.GetBytes("从PC指定IP应答----------------------------"), respondTo);
- }
- }
- catch (Exception ex) { MessageBox.Show(ex.ToString()); break; }
- }
- });
- tRecvt.IsBackground = true;
- tRecvt.Start();
PC上的接收和广播可以一起运行,由于广播代码增加了应答,而应答指定发给9999端口,也就是自身,所以一起运行会触发循环。
虚拟机上的输出:
- recvfrom 192.168.136.133 物理广播 255.255.255.255
- finish
- iface IP address Netmask
- lo 1.0.0.0
- ens33 2.0.0.0
- lo 127.0.0.1 255.0.0.0
- 发送给 127.255.255.255
- recvfrom 127.0.0.1 网段广播 127.255.255.255
- ens33 192.168.136.133 255.255.255.0
- 发送给 192.168.136.255
- recvfrom 192.168.136.133 网段广播 192.168.136.255
- lo 0.0.0.0 0.0.0.0
- 发送给 255.255.255.255
- recvfrom 192.168.136.133 网段广播 255.255.255.255
- ens33 0.0.0.0 0.0.0.0
- 发送给 255.255.255.255
- recvfrom 192.168.136.133 网段广播 255.255.255.255
- recvfrom 192.168.136.1 从PC指定IP应答----------------------------
- recvfrom 192.168.136.1 从PC指定IP应答----------------------------
- recvfrom 192.168.136.1 从PC指定IP应答----------------------------
- recvfrom 192.168.136.1 从PC指定IP应答----------------------------
输出信息表明从虚拟机发往“255.255.255”和同网段的广播虚拟机和PC全部都收到了。屏蔽掉枚举IP的代码,只发送“255.255.255”广播仍然收到了PC的应答,说虚拟机上物理广播时可以使用的。PC上只运行广播的代码,虚拟机一样可以收到广播信息,但是这不符合发现未知IP设备的这个目的。
从设备上执行,在PC上无法收到设备的物理广播,只能收到网段广播。
经过以上测试,网段广播可用,物理广播却无法在设备和PC之间使用,甚至PC上的物理广播根本没法执行。
但是我的目的是发现未知IP的设备,这就很尴尬了。只能在已知网段的前提下进行,先设置IP,再去广播,这和已经知道IP还有多大区别呢?
结合之前我在安卓上的测试,app设置热点已经被禁止了,所以任何可以方便地进行网络发现的方法基于安全理由都处于已经被禁止或即将被禁止中。
有些软件似乎还有基于网卡发现设备的功能,是怎么实现的呢?
用wireshark做了抓包,规则为udp.port,发现所有的网段广播都收到了,物理广播却没有收到,这样就有一个新思路:用pcap抓包,从udp源地址即可知道设备的地址。我还没有用代码来实现,不过既然wireshark可以,程序应该也可以,毕竟wireshark底层用的也是pcap(winpcap)。
pcap我在linux上写过程序,win上应该差不多。待写出来了再上代码。
winpcap已经过时,win10以上用Npcap(网站互相有链接)。
(这里是结束)