examples/tcp_ip.rb
require 'bindata'
# This is a simple protocol analyser for examining IPv4 packets
# captured with libpcap on an ethernet network.
#
# The dump file can be obtained like this:
#
# sudo tcpdump -i eth0 -s 0 -n -w dump.pcap
#
# Present MAC addresses in a human readable way
class MacAddr < BinData::Primitive
array :octets, type: :uint8, initial_length: 6
def set(val)
self.octets = val.split(/:/).collect(&:to_i)
end
def get
self.octets.collect { |octet| "%02x" % octet }.join(":")
end
end
# Present IP addresses in a human readable way
class IP_Addr < BinData::Primitive
array :octets, type: :uint8, initial_length: 4
def set(val)
self.octets = val.split(/\./).collect(&:to_i)
end
def get
self.octets.collect { |octet| "%d" % octet }.join(".")
end
end
# TCP Protocol Data Unit
class TCP_PDU < BinData::Record
endian :big
uint16 :src_port
uint16 :dst_port
uint32 :seq
uint32 :ack_seq
bit4 :doff
bit4 :res1
bit2 :res2
bit1 :urg
bit1 :ack
bit1 :psh
bit1 :rst
bit1 :syn
bit1 :fin
uint16 :window
uint16 :checksum
uint16 :urg_ptr
string :options, read_length: :options_length_in_bytes
rest :payload
def options_length_in_bytes
(doff - 5 ) * 4
end
end
# UDP Protocol Data Unit
class UDP_PDU < BinData::Record
endian :big
uint16 :src_port
uint16 :dst_port
uint16 :len
uint16 :checksum
rest :payload
end
# IP Protocol Data Unit
class IP_PDU < BinData::Record
endian :big
bit4 :version, asserted_value: 4
bit4 :header_length
uint8 :tos
uint16 :total_length
uint16 :ident
bit3 :flags
bit13 :frag_offset
uint8 :ttl
uint8 :protocol
uint16 :checksum
ip_addr :src_addr
ip_addr :dest_addr
string :options, read_length: :options_length_in_bytes
buffer :payload, length: :payload_length_in_bytes do
choice :payload, selection: :protocol do
tcp_pdu 6
udp_pdu 17
rest :default
end
end
def header_length_in_bytes
header_length * 4
end
def options_length_in_bytes
header_length_in_bytes - options.rel_offset
end
def payload_length_in_bytes
total_length - header_length_in_bytes
end
end
# Ethernet Frame - NOTE only ipv4 is supported
class Ether < BinData::Record
IPV4 = 0x0800
endian :big
mac_addr :dst
mac_addr :src
uint16 :ether_type
choice :payload, selection: :ether_type do
ip_pdu IPV4
rest :default
end
end
class Pcap
def initialize(filename)
@filename = filename
end
def each_record
File.open(@filename) do |io|
file = PcapFile.read(io)
file.records.each do |rec|
yield rec.data
end
end
end
class PcapFile < BinData::Record
endian :little
struct :head do
uint32 :magic
uint16 :major
uint16 :minor
int32 :this_zone
uint32 :sig_figs
uint32 :snaplen
uint32 :linktype
end
array :records, read_until: :eof do
uint32 :ts_sec
uint32 :ts_usec
uint32 :incl_len
uint32 :orig_len
string :data, length: :incl_len
end
end
end
require 'pp'
unless File.exist?('dump.pcap')
puts "No dump file found. Create one with: sudo tcpdump -i eth0 -s 0 -n -w dump.pcap"
exit 1
end
cap = Pcap.new('dump.pcap')
cap.each_record do |str|
pp Ether.read(str)
end