211 lines
5.5 KiB
Ruby
211 lines
5.5 KiB
Ruby
module PacketFu
|
|
|
|
# UDPHeader is a complete UDP struct, used in UDPPacket. Many Internet-critical protocols
|
|
# rely on UDP, such as DNS and World of Warcraft.
|
|
#
|
|
# For more on UDP packets, see http://www.networksorcery.com/enp/protocol/udp.htm
|
|
#
|
|
# ==== Header Definition
|
|
# Int16 :udp_src
|
|
# Int16 :udp_dst
|
|
# Int16 :udp_len Default: calculated
|
|
# Int16 :udp_sum Default: 0. Often calculated.
|
|
# String :body
|
|
class UDPHeader < Struct.new(:udp_src, :udp_dst, :udp_len, :udp_sum, :body)
|
|
|
|
include StructFu
|
|
|
|
def initialize(args={})
|
|
super(
|
|
Int16.new(args[:udp_src]),
|
|
Int16.new(args[:udp_dst]),
|
|
Int16.new(args[:udp_len] || udp_calc_len),
|
|
Int16.new(args[:udp_sum]),
|
|
StructFu::String.new.read(args[:body])
|
|
)
|
|
end
|
|
|
|
# Returns the object in string form.
|
|
def to_s
|
|
self.to_a.map {|x| x.to_s}.join
|
|
end
|
|
|
|
# Reads a string to populate the object.
|
|
def read(str)
|
|
force_binary(str)
|
|
return self if str.nil?
|
|
self[:udp_src].read(str[0,2])
|
|
self[:udp_dst].read(str[2,2])
|
|
self[:udp_len].read(str[4,2])
|
|
self[:udp_sum].read(str[6,2])
|
|
self[:body].read(str[8,str.size])
|
|
self
|
|
end
|
|
|
|
# Setter for the UDP source port.
|
|
def udp_src=(i); typecast i; end
|
|
# Getter for the UDP source port.
|
|
def udp_src; self[:udp_src].to_i; end
|
|
# Setter for the UDP destination port.
|
|
def udp_dst=(i); typecast i; end
|
|
# Getter for the UDP destination port.
|
|
def udp_dst; self[:udp_dst].to_i; end
|
|
# Setter for the length field. Usually should be recalc()'ed instead.
|
|
def udp_len=(i); typecast i; end
|
|
# Getter for the length field.
|
|
def udp_len; self[:udp_len].to_i; end
|
|
# Setter for the checksum. Usually should be recalc()'ed instad.
|
|
def udp_sum=(i); typecast i; end
|
|
# Getter for the checksum.
|
|
def udp_sum; self[:udp_sum].to_i; end
|
|
|
|
# Returns the true length of the UDP packet.
|
|
def udp_calc_len
|
|
body.to_s.size + 8
|
|
end
|
|
|
|
# Recalculates calculated fields for UDP.
|
|
def udp_recalc(args=:all)
|
|
arg = arg.intern if arg.respond_to? :intern
|
|
case args
|
|
when :udp_len
|
|
self.udp_len = udp_calc_len
|
|
when :all
|
|
self.udp_recalc(:udp_len)
|
|
else
|
|
raise ArgumentError, "No such field `#{arg}'"
|
|
end
|
|
end
|
|
|
|
# Equivalent to udp_src.to_i
|
|
def udp_sport
|
|
self.udp_src
|
|
end
|
|
|
|
# Equivalent to udp_src=
|
|
def udp_sport=(arg)
|
|
self.udp_src=(arg)
|
|
end
|
|
|
|
# Equivalent to udp_dst
|
|
def udp_dport
|
|
self.udp_dst
|
|
end
|
|
|
|
# Equivalent to udp_dst=
|
|
def udp_dport=(arg)
|
|
self.udp_dst=(arg)
|
|
end
|
|
|
|
end
|
|
|
|
# UDPPacket is used to construct UDP Packets. They contain an EthHeader, an IPHeader, and a UDPHeader.
|
|
#
|
|
# == Example
|
|
#
|
|
# udp_pkt = PacketFu::UDPPacket.new
|
|
# udp_pkt.udp_src=rand(0xffff-1024) + 1024
|
|
# udp_pkt.udp_dst=53
|
|
#
|
|
# udp_pkt.ip_saddr="1.2.3.4"
|
|
# udp_pkt.ip_daddr="10.20.30.40"
|
|
#
|
|
# udp_pkt.recalc
|
|
# udp_pkt.to_f('/tmp/udp.pcap')
|
|
#
|
|
# == Parameters
|
|
#
|
|
# :eth
|
|
# A pre-generated EthHeader object.
|
|
# :ip
|
|
# A pre-generated IPHeader object.
|
|
# :flavor
|
|
# TODO: Sets the "flavor" of the UDP packet. UDP packets don't tend have a lot of
|
|
# flavor, but their underlying ip headers do.
|
|
# :config
|
|
# A hash of return address details, often the output of Utils.whoami?
|
|
class UDPPacket < Packet
|
|
|
|
attr_accessor :eth_header, :ip_header, :udp_header
|
|
|
|
def initialize(args={})
|
|
@eth_header = EthHeader.new(args).read(args[:eth])
|
|
@ip_header = IPHeader.new(args).read(args[:ip])
|
|
@ip_header.ip_proto=0x11
|
|
@udp_header = UDPHeader.new(args).read(args[:icmp])
|
|
@ip_header.body = @udp_header
|
|
@eth_header.body = @ip_header
|
|
@headers = [@eth_header, @ip_header, @udp_header]
|
|
super
|
|
udp_calc_sum
|
|
end
|
|
|
|
# udp_calc_sum() computes the UDP checksum, and is called upon intialization.
|
|
# It usually should be called just prior to dropping packets to a file or on the wire.
|
|
def udp_calc_sum
|
|
# This is /not/ delegated down to @udp_header since we need info
|
|
# from the IP header, too.
|
|
checksum = (ip_src.to_i >> 16)
|
|
checksum += (ip_src.to_i & 0xffff)
|
|
checksum += (ip_dst.to_i >> 16)
|
|
checksum += (ip_dst.to_i & 0xffff)
|
|
checksum += 0x11
|
|
checksum += udp_len.to_i
|
|
checksum += udp_src.to_i
|
|
checksum += udp_dst.to_i
|
|
checksum += udp_len.to_i
|
|
if udp_len.to_i >= 8
|
|
# For IP trailers. This isn't very reliable. :/
|
|
real_udp_payload = payload.to_s[0,(udp_len.to_i-8)]
|
|
else
|
|
# I'm not going to mess with this right now.
|
|
real_udp_payload = payload
|
|
end
|
|
chk_payload = (real_udp_payload.size % 2 == 0 ? real_udp_payload : real_udp_payload + "\x00")
|
|
chk_payload.unpack("n*").each {|x| checksum = checksum+x}
|
|
checksum = checksum % 0xffff
|
|
checksum = 0xffff - checksum
|
|
checksum == 0 ? 0xffff : checksum
|
|
@udp_header.udp_sum = checksum
|
|
end
|
|
|
|
# udp_recalc() recalculates various fields of the UDP packet. Valid arguments are:
|
|
#
|
|
# :all
|
|
# Recomputes all calculated fields.
|
|
# :udp_sum
|
|
# Recomputes the UDP checksum.
|
|
# :udp_len
|
|
# Recomputes the UDP length.
|
|
def udp_recalc(args=:all)
|
|
case args
|
|
when :udp_len
|
|
@udp_header.udp_recalc
|
|
when :udp_sum
|
|
udp_calc_sum
|
|
when :all
|
|
@udp_header.udp_recalc
|
|
udp_calc_sum
|
|
else
|
|
raise ArgumentError, "No such field `#{arg}'"
|
|
end
|
|
end
|
|
|
|
# Peek provides summary data on packet contents.
|
|
def peek(args={})
|
|
peek_data = ["U "]
|
|
peek_data << "%-5d" % self.to_s.size
|
|
peek_data << "%-21s" % "#{self.ip_saddr}:#{self.udp_sport}"
|
|
peek_data << "->"
|
|
peek_data << "%21s" % "#{self.ip_daddr}:#{self.udp_dport}"
|
|
peek_data << "%23s" % "I:"
|
|
peek_data << "%04x" % self.ip_id
|
|
peek_data.join
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
# vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby
|