Skip to content

Instantly share code, notes, and snippets.

@sirupsen
Created May 28, 2021 14:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sirupsen/a54ce38cf99ef17512fdbea3840a0168 to your computer and use it in GitHub Desktop.
Save sirupsen/a54ce38cf99ef17512fdbea3840a0168 to your computer and use it in GitHub Desktop.
require 'socket'
def internet_checksum(packet) # https://tools.ietf.org/html/rfc1071
packet += [0].pack('C') if packet.size.odd? # since we read 2 bytes at once, append 0 if odd
sum = packet.unpack("n#{packet.size/2}").sum
sum = (sum & 0xffff) + (sum >> 16) while (sum >> 16 > 0) # fold 32 bits -> 16
~sum & 0xffff # 1s complement
end
down = []
ping_times = []
Signal.trap("SIGINT") do
puts
sorted_ping_times = ping_times.sort
median = sorted_ping_times[sorted_ping_times.size/2]
puts "packet loss: #{(down.size.to_f / (ping_times.size + down.size))*100}%"
puts "min: #{sorted_ping_times.first * 1000}ms"
puts "p50: #{median * 1000}ms"
puts "p95: #{sorted_ping_times[((sorted_ping_times.size/100.0)*95).to_i] * 1000}ms"
puts "p99: #{sorted_ping_times[((sorted_ping_times.size/100.0)*99).to_i] * 1000}ms"
puts "max: #{sorted_ping_times.last * 1000}ms"
sum_difference_squared = sorted_ping_times.inject(0) { |sum, ping_time|
sum + ((ping_time * 1000 - median * 1000) ** 2)
}
variance = sum_difference_squared / ping_times.size
puts "variance: #{variance}"
puts "std deviation: #{Math.sqrt(variance)}"
exit
end
socket = Socket.new(Socket::PF_INET, Socket::SOCK_RAW, Socket::IPPROTO_ICMP)
socket.bind(Socket.pack_sockaddr_in(0, "0.0.0.0"))
# https://support.google.com/a/answer/7582554?hl=en#zippy=%2Cmeasure-latency
# destination = Socket.pack_sockaddr_in(0, "192.168.1.1")
destination = Socket.pack_sockaddr_in(0, "1.1.1.1")
identifier = Process.pid & 0xFFFF
seq = 0
loop do
before = Process.clock_gettime(Process::CLOCK_MONOTONIC)
# byte: type, byte: subcode, short: checksum, short: identifier, short: seq, data
seq = (seq + 1) % (2 ** 16)
msg = [8, 0, 0, identifier, seq, ""] # 8 is ICMP ECHO request
msg[2] = internet_checksum(msg.pack("C2 n3"))
begin
socket.send(msg.pack("C2 n3"), 0, destination)
rescue => e
down << [Time.now]
end
# TODO: implement timeout + exception handling, because this will run for days
loop do
data = socket.recvfrom(1500).first
# 20 bytes ICMP header, first byte after that should be type=ICMP ECHO 0
# https://en.wikipedia.org/wiki/Ping_(networking_utility)
if data[20].unpack("C2").first.zero?
# ICMP doesn't have ports to distinguish applications like TCP/UDP, so we
# need to do this ourselves based on the identifier and response sequence.
# Otherwise if we ran multiple `ping` in parallel, it'd count wrong!
response_identifier, response_seq = data[24, 4].unpack('n3')
if response_identifier == identifier && seq == response_seq
duration_s = Process.clock_gettime(Process::CLOCK_MONOTONIC) - before
ping_times << duration_s
puts "#{(duration_s * 1000).round(1)} ms"
break
end
end
end
sleep 1
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment