使用广播和回声进行主机发现

网络主机发现是试图获取连接到网络的计算机的地址。上周我写了一篇关于使用Perl进行单播方法的文章,该方法遍历网络子网中的每个地址,逐个发送消息到每个地址,以查看是否有任何主机响应。本周我一直在使用广播和回声的替代方法进行研究。

ICMP和回声

Internet控制消息协议(ICMP)是一种网络协议,网络设备使用它来相互协调。ICMP消息包含类型和代码,这些代码具有预定义的含义

类型为8的ICMP消息表示回声请求,主机应使用类型为0(回声响应)的ICMP消息进行响应。为了发现网络上的主机,我可以向网络发送回声请求,并捕获收到的任何回声响应的IP地址。而不是遍历子网中可能的所有IP地址,我可以向广播IP地址:255.255.255.255发送回声请求,消息将自动发送到网络上的每个主机。

如果你正在运行现代Linux,你可以在命令行中使用ping测试此功能(其他版本可能不需要“-b”开关)

$ ping -b 255.255.255.255
WARNING: pinging broadcast address
PING 255.255.255.255 (255.255.255.255) 56(84) bytes of data.
64 bytes from 192.168.1.4: icmp_seq=1 ttl=64 time=92.9 ms
64 bytes from 192.168.1.4: icmp_seq=2 ttl=64 time=2.04 ms
64 bytes from 192.168.1.4: icmp_seq=3 ttl=64 time=136 ms
...

在这里,你可以看到我的网络上的另一个主机正在地址192.168.1.4上响应

在Perl中实现回声

可以使用核心Perl模块实现ping。也就是说,如果安装了Perl,这个脚本应该可以工作

#!/usr/bin/perl
use strict;
use warnings;
use Socket;
use Net::Ping;

# the checksum must be correct else hosts will ignore the request
my $msg_checksum = Net::Ping->checksum(pack("C2 n3",8,0,0,0,1));
my $msg = pack("C2 n3", 8, 0, $msg_checksum, 0, 1);

socket(my $socket, AF_INET, SOCK_RAW, getprotobyname('icmp'));
setsockopt($socket, SOL_SOCKET, SO_BROADCAST, 1);
send($socket, $msg, 0, sockaddr_in(0, inet_aton('255.255.255.255')));
bind($socket,sockaddr_in(0,inet_aton(0)));

while (1)
{
  my $addr = recv($socket, my $data, 1024, 0);
  my ($tmp, $tos, $len, $id, $offset, $tt, $proto, $checksum,
    $src_ip, $dest_ip, $options) = unpack('CCnnnCCnNNa*', $data);

  if ($dest_ip != 4294967295) # destination != 255.255.255.255
  {
    my ($port, $peer) = sockaddr_in($addr);
    printf "%s bytes from %s\n", length($data), inet_ntoa($peer);
  }
}

此脚本首先导入SocketNet::Ping模块——这两者都是Perl核心分发的一部分。它使用来自Net::Pingchecksum函数来计算消息校验和。校验和很重要,因为如果它不正确,主机将不会响应。脚本将代码、类型、校验和偏移量打包到$msg中。

然后脚本创建一个广播套接字,并将消息发送到广播地址(255.255.255.255)。然后套接字绑定到网络地址,脚本进入一个while循环,尝试使用recv从套接字读取数据。任何接收到的数据都会被解包,并将数据包地址保存到$addr中。

解包消息中的源和目的IP字段被存储为32位整数,因此脚本忽略与广播地址整数匹配的目的地址的包,因为这个消息是由脚本发送的。然后脚本解码数据包地址并打印结果。

在我的网络上运行此脚本,我可以看到与ping返回相同的同一主机

$ sudo ./livehost_echo                                 
28 bytes from 192.168.1.4

指纹识别主机

这种技术的主要问题是它只能发现对广播请求做出响应的主机,而许多主机并不这样做。例如,Chromebook、智能手机和Linux机器通常不会响应(OSX机器和许多Windows版本则会)。这可以是一个优势:因为广播的响应率与单播不同,回声脚本可以与单播结合使用来指纹主机。如果一个机器对单播消息做出响应但不对广播做出响应,我们可以了解到有关该主机的某些信息。例如,如果我在我的家庭网络中使用livehost_scanner脚本

sudo $(which perl) livehost_scanner                                                                                                                  
Gateway IP: 192.168.1.1
Starting scan
192.168.1.1 10:0d:7f:81:31:c2
192.168.1.2 5c:c5:d4:47:0a:13 (this machine)
192.168.1.7 38:e7:d8:00:9a:d5
192.168.1.4 e0:ac:cb:5e:d5:da
192.168.1.10 cc:3d:82:60:4b:95

我发现还有2个其他活动主机(不包括路由器)出现,但没有响应回显请求。回显脚本可以调整以发送其他类型的ICMP消息,例如时间戳和子网掩码,这些消息可以进一步识别主机。

进一步思考

回显脚本使用广播技术,这仅在IPv4网络上有效。IPv6网络支持多播,但需要修改脚本。有趣的是,单个IPv6子网中潜在地址的数量(我认为)使得单播技术变得多余。

回显脚本另一个问题是,因为它打开原始套接字,需要root权限才能运行。另一方面,ping工具带有setuserid权限安装,并作为root运行,而不管用户的自身权限如何。

有用资源

在编写此脚本的过程中,我学到了很多关于套接字和网络编程的知识。Lincoln Stein的《Perl网络编程》是理解套接字及其使用的神秘调用的宝贵资源。如果您考虑使用套接字,IO::Socket模块比Socket模块(也是核心部分)提供了一个更干净的接口。优秀的NetPacket分发的源代码对于理解如何解析数据包很有用。


本文最初发布在PerlTricks.com

标签

David Farrell

David是一位专业程序员,经常推文博客关于代码和编程艺术。

浏览他们的文章

反馈

这篇文章有什么问题吗?请通过在GitHub上打开问题或拉取请求来帮助我们。