Last active
November 2, 2021 15:32
-
-
Save palkan/74885c6e3fee6a0c3e63cd606740fd63 to your computer and use it in GitHub Desktop.
tcpdump -> pretty http2 packets
This file contains 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
# frozen_string_literal: true | |
# Example usage: | |
# | |
# tcpdump -i lo0 'port 3010' -v -X -l | ruby frame_display.rb | |
# | |
require 'bundler/inline' | |
gemfile(true, quiet: true) do | |
source 'https://rubygems.org' | |
gem 'http-2' | |
# gem 'debug' # uncomment for debugging | |
end | |
require 'http/2/buffer' | |
require 'http/2/framer' | |
def hex_to_array(str) | |
str.gsub(/\s+/m, '').scan(/../).map { _1.to_i(16).chr } | |
end | |
def parse_ipv4(str, **opts) | |
arr = hex_to_array(str) | |
payload = arr[52...] | |
return if payload.nil? | |
parse_frame(payload.join, **opts) | |
end | |
def parse_frame(source, **opts) | |
buffer = HTTP2::Buffer.new(source) | |
framer = HTTP2::Framer.new | |
loop do | |
frame = framer.parse(buffer) | |
break unless frame | |
print_frame(frame, **opts) | |
end | |
end | |
TYPE_TO_COLOR = { | |
headers: "\e[34m", # blue | |
data: "\e[32m" # green | |
}.freeze | |
DEFAULT_COLOR = "\e[33m" # yellow | |
def print_frame(frame, direction: nil) | |
type = frame.delete(:type) | |
clr = TYPE_TO_COLOR.fetch(type, DEFAULT_COLOR) | |
annotation = +"Stream: #{frame.delete(:stream)} Length: #{frame.delete(:length)}" | |
annotation << " (#{direction})" if direction | |
puts "#{clr}#{type.to_s.upcase}\e[0m [\e[36m#{frame.delete(:flags).map do | |
_1.to_s.upcase | |
end.join(' ')}\e[0m] \e[90m# #{annotation}\e[0m\n" | |
frame.delete(:payload).then do |payload| | |
next unless payload | |
puts " #{payload.inspect}" | |
end | |
puts " #{frame.inspect}" unless frame.empty? | |
puts | |
end | |
def read_dump(io) | |
current_packet = nil | |
direction = nil | |
expected_length = nil | |
io.each_line do |line| | |
case line | |
# 21:52:25.397055 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 64, | |
when /^\d{2}:\d{2}:\d{2}\.\d+\sIP.+length (\d+)/ | |
current_packet = [] | |
expected_length = Regexp.last_match[1].to_i * 2 | |
# localhost.gw > localhost.64902: Flags [.], cksum 0xfe28 (incorrect -> 0x158b), ack 110, win | |
when /^\s+([\w\-.]+ > [\w\-.]+): Flags/ | |
direction = Regexp.last_match[1] | |
# 0x0000: 4500 0034 0000 4000 4006 0000 7f00 0001 E..4..@.@....... | |
when /^\s+0x[a-f\d]{4}:\s\s(([a-f\d]{4}\s)*[a-f\d]{2,})\s\s/ | |
current_packet ||= [] | |
bytes = Regexp.last_match[1].split(/\s+/).join.chars | |
current_packet.concat bytes | |
parse_ipv4(current_packet.join, direction: direction) if current_packet.size == expected_length | |
end | |
end | |
end | |
# Pipe tcdump | |
if $stdin.stat.pipe? | |
read_dump($stdin) | |
elsif ARGV[0] | |
read_dump(ARGF) | |
else | |
raise 'Please, provide path to frame dump file as the first argument or pipe tcpdump to the script' | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment