应该可以使用Ruby套接字库发送和接收ICMP数据包,但我没有看到任何关于此的良好文档。
我不想使用net-ping,icmp,ping,以及因跨平台问题而失败的所有其他库,需要devkit和自定义构建,在构建过程中失败,被忽略并且没有经过很长时间的更新,和/或只是一般的马车。
有没有人有关于如何完成此任务的任何好文档?我想发送ICMP回应回复,而不是TCP或UDP数据包。
答案 0 :(得分:2)
在他的Net-ping项目中阅读Daniel Berger的代码,我能够看到他是如何做到的。
答案 1 :(得分:0)
我最近挖了这个问题,想做一个自成一体的答案。我在开发中使用 Linux 或 macOS,在生产中使用 Linux。
在 2011 年,a patch was introduced 允许创建一个套接字,内核在其中处理一些 ICMP 内容,例如提供 ID 和使用回显请求计算校验和。它也适用于 macOS。我用 ICMP 回显请求和回复做了一些测试。
在 macOS 下:
在 Linux 下,必须相应地设置权限以允许来自组的用户在未以 root
运行的情况下创建套接字。在 macOS 上不需要它。
sysctl net.ipv4.ping_group_range='1000 1000'
某些代码已从 net-ping
中提取或改编。 Unix Network Programming: The Sockets Networking Api 书(第 1 卷,第 3 版,Stevens、Fenner 和 Rudoff,Addison-Wesley,2004 年)非常有趣,特别是有关原始套接字的章节(第 28 章)及其第 28.4 节(“原始套接字输入”)和 28.5(“ping 程序”)。
#!/usr/bin/env ruby
require 'socket'
def bin_to_hex(s, sep = " ")
s.each_byte.map { |b| "%02x" % b.to_i }.join(sep)
end
def checksum(msg)
length = msg.length
num_short = length / 2
check = msg.unpack("n#{num_short}").sum
if length % 2 > 0
check += msg[length-1, 1].unpack1('C') << 8
end
check = (check >> 16) + (check & 0xffff)
return (~((check >> 16) + check) & 0xffff)
end
def send_ping(socket, host, seq, data)
id = 0
checksum = 0
icmp_packet = [8, 0, checksum, id, seq].pack('C2 n3') << data
puts "icmp_packet bef checksum: #{bin_to_hex(icmp_packet)}"
checksum = checksum(icmp_packet)
icmp_packet = [8, 0, checksum, id, seq].pack('C2 n3') << data
puts "icmp_packet aft checksum: #{bin_to_hex(icmp_packet)}"
saddr = Socket.pack_sockaddr_in(0, host)
socket.send(icmp_packet, 0, saddr)
return icmp_packet
end
def receive_ping(socket, timeout)
io_array = select([socket], nil, nil, timeout)
if io_array.nil? || io_array[0].empty?
return nil, nil
end
# length is either 12 bytes of ICMP alone or 20 bytes of IP header + 12 bytes of ICMP = 32 bytes
# data = socket.recv(32) # IP header 20 + 12
data = socket.recv(32)
puts "received packet: #{bin_to_hex(data)}"
rcvd_at = Time.now
if data.size == 32
if data.unpack1("C") == 0x45
# We have an IP header
offset = 20
else
# Looks like an IP header but it is not!
return rcvd_at, nil
end
else
# data.size == 12
offset = 0
end
icmp_type, icmp_code = data[0 + offset, 2].unpack('C2')
if icmp_type == 0 && icmp_code == 0
echo_reply_id, echo_reply_seq = data[4 + offset, 4].unpack('n2')
# Check if using a raw socket (SOCK_RAW)
# Means we need sent id (and seq if we want to)
# if id == echo_reply_id && seq == echo_reply_seq
return rcvd_at, data[offset..]
# end
end
return rcvd_at, nil
end
sock = Socket.open(Socket::PF_INET, Socket::SOCK_DGRAM, Socket::IPPROTO_ICMP)
# sock = Socket.open(Socket::PF_INET, Socket::SOCK_RAW, Socket::IPPROTO_ICMP)
# No need unless we use a raw socket
# id = Process.pid & 0xffff
seq = 1
sent_at = Time.now
sent_at_ms = (sent_at.hour * 3600 + sent_at.min * 60 + sent_at.sec) * 1000 + sent_at.tv_nsec / 1000000
sent = send_ping(sock, ARGV[0], seq, [sent_at_ms].pack("N"))
puts "sent icmp packet: #{bin_to_hex(sent)}"
# The loop is necessary in case of a raw socket because perhaps we did not receive a reply for our request
# loop do
rcvd_at, rcvd = receive_ping(sock, 5000)
if rcvd
rcvd_at_ms = (rcvd_at.hour * 3600 + rcvd_at.min * 60 + rcvd_at.sec) * 1000 + rcvd_at.tv_nsec / 1000000
sent_at_ms = rcvd[8, 4].unpack1("N")
latency = rcvd_at_ms - sent_at_ms
puts "size: #{rcvd.size}, latency: #{latency}, rcvd icmp: #{bin_to_hex(rcvd)}"
# break
# else
# puts "received bytes is not our reply"
end
# end
sock.close
在 macOS 上我们得到:
$ ./ping.rb google.com
icmp_packet bef checksum: 08 00 00 00 00 00 00 01 02 17 b3 5e
icmp_packet aft checksum: 08 00 42 89 00 00 00 01 02 17 b3 5e
sent icmp packet: 08 00 42 89 00 00 00 01 02 17 b3 5e
received packet: 45 60 0c 00 00 00 00 00 73 01 c2 10 ac d9 17 6e c0 a8 00 7d 00 00 4a 89 00 00 00 01 02 17 b3 5e
size: 12, latency: 29, rcvd icmp: 00 00 4a 89 00 00 00 01 02 17 b3 5e
在 Debian 10 上:
$ ./ping.rb google.com
icmp_packet bef checksum: 08 00 00 00 00 00 00 01 02 18 e0 f9
icmp_packet aft checksum: 08 00 14 ed 00 00 00 01 02 18 e0 f9
sent icmp packet: 08 00 14 ed 00 00 00 01 02 18 e0 f9
received packet: 00 00 1c 9c 00 51 00 01 02 18 e0 f9
size: 12, latency: 14, rcvd icmp: 00 00 1c 9c 00 51 00 01 02 18 e0 f9
注意接收到的数据包之间的区别。请注意,我们以自午夜(当地时间)以来的毫秒数形式将当前时间放入数据包中,并且我们没有考虑第二天收到回复的请求(例如,在 23:59:59 发送并在第二天 00:00:01 收到,2 秒后)。
如果我们使用原始套接字,则需要一些代码。