Created
May 28, 2021 14:21
-
-
Save sirupsen/a54ce38cf99ef17512fdbea3840a0168 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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