packetman 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a30c3ba57b035cf5927580b722a5651aefc55317
4
- data.tar.gz: 68c4aa0507610985b538da8e6b8b432ffbd6aa49
3
+ metadata.gz: 6ee06237cff9b2ffb77984c3d3ed1447e9fe4ba8
4
+ data.tar.gz: 58b85bd5f40726dbe58271425c6247556659dcfe
5
5
  SHA512:
6
- metadata.gz: 976a934d36e13ceb1593728d7067198bbbe78d036da0a600ffa58703483709ca35c813feb3198aa3575ed9f6c9ca680a96572c31a05358ee542438c5d248fec3
7
- data.tar.gz: 727bf261f5a87940af9abd8e636272dce8d1e2de7746976b2079b2b6dff2717da849fbd780b0da17df47ee2fed1a981f48ae0e7ce7cdb52ad7e61cd532f8354f
6
+ metadata.gz: cfa7fe1df88846174e93d98003b4399da5b97162b3ccf8c9a23895d87e4d16a69da654c1da5772dd98eb0ce06afe97033b1adbb36913e3e1c470efd3a3dfe964
7
+ data.tar.gz: 48890d5a660428531b7d17f6338bcba85f142ec6b76ce09519632fddd2a7536f9079432e89065f817a5de5a1e07fc997bd24e3ee5ec9b45c5d70211314353a68
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Packetman
2
2
 
3
- Advanced tcpdump and Wireshark capture generator.
3
+ Advanced tcpdump and Wireshark filter string generator.
4
4
 
5
5
  [![Gem Version](https://badge.fury.io/rb/packetman.svg)](http://badge.fury.io/rb/packetman)
6
6
  [![Test Coverage](https://codeclimate.com/github/jescholl/packetman/badges/coverage.svg)](https://codeclimate.com/github/jescholl/packetman/coverage)
@@ -38,7 +38,40 @@ Or install it yourself as:
38
38
 
39
39
  ## Usage
40
40
 
41
- TODO: Write usage instructions here
41
+ $ packetman -h
42
+
43
+ Usage: packetman [OPTIONS] FILTER_STRING
44
+ -p, --protocol PROTO Transport Protocol (tcp,udp,icmp)
45
+ -t, --transport OFFSET starts at transport header instead of data payload
46
+ -r, --radix RADIX Treat FILTER_STRING as RADIX instead of String
47
+ -o, --offset OFFSET Offset in bits
48
+ -b, --byte-offset Use 8-bit bytes instead of bits for offset
49
+ -w, --wildcard [CHARACTER=?] Treat CHARACTER as single-character wildcard
50
+ -v, --version Show version
51
+
52
+ Create and use a filter string to capture all HTTP GET requests to `/foo/bar`
53
+
54
+ $ sudo tcpdump -nA `packetman GET /foo/bar`
55
+ tcpdump: data link type PKTAP
56
+ tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
57
+ listening on pktap, link-type PKTAP (Packet Tap), capture size 262144 bytes
58
+ 16:49:04.516409 IP 127.0.0.1.54662 > 127.0.0.1.80: Flags [P.], seq 1488105913:1488105994, ack 1397163988, win 4121, options [nop,nop,TS val 875380202 ecr 2751916352], length 81: HTTP: GET /foo/bar HTTP/1.1
59
+ .....b....j...E.....@.@..S..
60
+ ..:.....PX...SG......75.....
61
+ 4-=....@GET /foo/bar HTTP/1.1
62
+ Host: localhost
63
+ User-Agent: curl/7.43.0
64
+ Accept: */*
65
+
66
+ Hexadecimal string with wildcards
67
+
68
+ $ packetman -r 16 -w '?' "A8C401???C200A"
69
+ tcp[((tcp[12:1] & 0xf0) >> 2) + 0:4] & 0xffffff00 = 0xa8c40100 && tcp[((tcp[12:1] & 0xf0) >> 2) + 4:2] & 0x0fff = 0x0c20 && tcp[((tcp[12:1] & 0xf0) >> 2) + 6:1] & 0xff = 0x0a
70
+
71
+ Base 4 string with wildcards and offset beginning at start of the TCP header
72
+
73
+ $ packetman -t -o 3 -r 4 -w i 1223iiii2212
74
+ tcp[0:4] & 0x1fe01fe0 = 0x0d6014c0
42
75
 
43
76
  ## Development
44
77
 
@@ -1,29 +1,56 @@
1
1
  ---
2
2
  tcp:
3
3
  table:
4
- Source Port: 16
5
- Destination Port: 16
6
- Sequence Number: 32
7
- Acknowledgement Number: 32
8
- Data Offset: 4
9
- RESERVED: 3
10
- ECN: 3
11
- Control Bits: 6
12
- Window: 16
13
- Checksum: 16
14
- Urgent Pointer: 16
15
- Options and Padding: 32
4
+ - - :value: Source Port
5
+ :colspan: 16
6
+ - :value: Destination Port
7
+ :colspan: 16
8
+ - :separator
9
+ - - :value: Sequence Number
10
+ :colspan: 32
11
+ - :separator
12
+ - - :value: Acknowledgement Number
13
+ :colspan: 32
14
+ - :separator
15
+ - - :value: Data Offset
16
+ :colspan: 4
17
+ - :value: RESERVED
18
+ :colspan: 3
19
+ - :value: ECN
20
+ :colspan: 3
21
+ - :value: Control Bits
22
+ :colspan: 6
23
+ - :value: Window
24
+ :colspan: 16
25
+ - :separator
26
+ - - :value: Checksum
27
+ :colspan: 16
28
+ - :value: Urgent Pointer
29
+ :colspan: 16
30
+ - :separator
31
+ - - :value: Options and Padding
32
+ :colspan: 32
16
33
  payload_query: '((tcp[12:1] & 0xf0) >> 2)'
17
34
  udp:
18
35
  table:
19
- Source Port: 16
20
- Destination Port: 16
21
- Length: 16
22
- Checksum: 16
36
+ - - :value: Source Port
37
+ :colspan: 16
38
+ - :value: Destination Port
39
+ :colspan: 16
40
+ - :separator
41
+ - - :value: Length
42
+ :colspan: 16
43
+ - :value: Checksum
44
+ :colspan: 16
23
45
  payload_query: 8
24
46
  icmp:
25
47
  table:
26
- Type: 8
27
- Code: 8
28
- Checksum: 16
29
- Type Specific Options: 32
48
+ - - :value: Type
49
+ :colspan: 8
50
+ - :value: Code
51
+ :colspan: 8
52
+ - :value: Checksum
53
+ :colspan: 16
54
+ - :separator
55
+ - - :value: Type Specific Options
56
+ :colspan: 32
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'packetman'
3
3
 
4
- search_str = Packetman.config.parse_opts
4
+ catch :exit do
5
+ search_str = Packetman.config.parse_opts
5
6
 
6
- puts Packetman::Filter.new(search_str)
7
+ puts Packetman::Filter.new(search_str)
8
+ end
@@ -2,24 +2,31 @@ module Packetman
2
2
  class Clause
3
3
  include ConfigMethods
4
4
 
5
- attr_accessor :search, :mask, :offset
5
+ attr_accessor :search, :mask, :start_bit
6
6
 
7
- def initialize(search, mask, offset)
7
+ def initialize(search, mask, start_bit)
8
8
  self.search = search
9
9
  self.mask = mask
10
- self.offset = offset
10
+ self.start_bit = start_bit
11
11
  end
12
12
 
13
- def start_byte(start_bit)
14
- "#{config.payload_query} + #{(config.offset_bits + start_bit)/8}"
13
+ # Address of first byte
14
+ def start_byte
15
+ [config.payload_query, (config.offset_bits + start_bit)/8].compact.join(' + ')
15
16
  end
16
17
 
17
- def data_address(start_bit, data_bits)
18
- "#{config.transport}[#{start_byte(start_bit)}:#{data_bits/8}]"
18
+ def num_bytes
19
+ Filter.bit_length(search)/8
19
20
  end
20
21
 
22
+ # Full address of the query data (eg. `tcp[0:4]`)
23
+ def data_address
24
+ "#{config.transport}[#{start_byte}:#{num_bytes}]"
25
+ end
26
+
27
+ # The whole filter clause fully assembled
21
28
  def to_s
22
- "#{data_address(offset, Filter.bit_length(search))} & #{mask} = #{search}"
29
+ "#{data_address} & #{mask} = #{search}"
23
30
  end
24
31
 
25
32
  end
@@ -2,11 +2,12 @@ require 'optparse'
2
2
 
3
3
  module Packetman
4
4
  class Config
5
- attr_accessor :transport, :application, :use_bytes, :radix, :start_with_transport, :offset, :wildcard
5
+ attr_accessor :transport, :application, :offset_type, :radix, :start_with_transport, :offset, :wildcard
6
6
 
7
7
  def initialize
8
- @transport = "tcp"
9
- @offset = 0
8
+ self.transport = "tcp"
9
+ self.offset = 0
10
+ self.offset_type = :bits
10
11
  end
11
12
 
12
13
  def protocols
@@ -18,7 +19,7 @@ module Packetman
18
19
  end
19
20
 
20
21
  def offset_bits
21
- if use_bytes
22
+ if offset_type == :bytes
22
23
  offset*8
23
24
  else
24
25
  offset
@@ -28,22 +29,24 @@ module Packetman
28
29
  def opts
29
30
  @opts ||= OptionParser.new do |opt|
30
31
  opt.banner = "Usage: #{File.basename($PROGRAM_NAME)} [OPTIONS] FILTER_STRING"
31
- opt.on("-p", "--protocol PROTO", protocols.keys, "Transport Protocol ( #{protocols.keys.join(',')})") { |v| self.transport = v }
32
+ opt.on("-p", "--protocol PROTO", protocols.keys, "Transport Protocol (#{protocols.keys.join(',')})") { |v| self.transport = v }
32
33
  opt.on("-t", "--transport", "OFFSET starts at transport header instead of data payload") { |v| self.start_with_transport = v }
33
34
  opt.on("-r", "--radix RADIX", Integer, "Treat FILTER_STRING as RADIX instead of String") { |v| self.radix = v }
34
35
  opt.on("-o", "--offset OFFSET", Integer, "Offset in bits") { |v| self.offset = v }
35
- opt.on("-b", "--byte-offset", "Use 8-bit bytes instead of bits for offset") { |v| self.use_bytes = v }
36
- opt.on("-w", "--wildcard [CHARACTER=?]", "Treat CHARACTER as single-character wildcard") { |v| raise "invalid wildcard" if v.to_s.length > 1; self.wildcard = v || '?' }
37
- opt.on("-v", "--version", "Show version") { puts Packetman::VERSION; exit }
36
+ opt.on("-b", "--byte-offset", "Use 8-bit bytes instead of bits for offset") { |v| self.offset_type = :bytes if v }
37
+ opt.on("-w", "--wildcard CHARACTER", "Treat CHARACTER as single-character wildcard") { |v| raise "invalid wildcard" if v.to_s.length > 1; self.wildcard = v }
38
+ opt.on("--table", "Show transport header table") { puts Packetman::Table.new; throw :exit }
39
+ opt.on("-v", "--version", "Show version") { puts Packetman::VERSION; throw :exit }
38
40
  end
39
41
  end
40
42
 
41
-
42
43
  def parse_opts
43
- filter_str = ARGV.pop
44
- raise "Invalid command line arguments" unless filter_str
45
- opts.parse!
46
- filter_str
44
+ unparsed_opts = opts.parse!
45
+ if unparsed_opts.length < 1
46
+ puts opts
47
+ throw :exit
48
+ end
49
+ unparsed_opts.join(" ")
47
50
  end
48
51
 
49
52
  end
@@ -13,33 +13,36 @@ module Packetman
13
13
  case num
14
14
  when /^0x/
15
15
  $'.length * bit_density(16)
16
- when /^0b/
17
- $'.length * bit_density(2)
18
16
  else
19
17
  nil
20
18
  end
21
19
  end
22
20
 
23
21
  def self.bit_density(radix=config.radix)
24
- (radix.nil?) ? 8 : Math.log2(radix).to_i
22
+ (radix.nil?) ? 8 : Math.log2(radix).ceil
25
23
  end
26
24
 
27
25
  def map_chr
28
- pad_right(input.scan(/./).map{ |chr| yield chr }.join)
26
+ shift_and_pad(input.scan(/./).map{ |chr| yield chr }.join)
29
27
  end
30
28
 
31
- def pad_right(bin_str)
32
- bin_str.ljust(desired_length, '0')
29
+ def shift_and_pad(bin_str)
30
+ #shift
31
+ bin_str.ljust(target_bit_length, '0').
32
+ #pad
33
+ rjust(target_bit_length + config.offset_bits % 8, '0')
33
34
  end
34
35
 
35
- def desired_length
36
- ((input.length + config.offset_bits)/8.to_f).ceil*8 - config.offset_bits
36
+ def target_bit_length
37
+ ((input.length*self.class.bit_density + config.offset_bits)/8.to_f).ceil*8 - config.offset_bits
37
38
  end
38
39
 
40
+ # Mask for 1 character of current radix
39
41
  def radix_mask
40
42
  ("1"*self.class.bit_density).to_i(2)
41
43
  end
42
44
 
45
+ # Mask string for _chr_ substituting wildcards as necessary
43
46
  def mask_chr(chr)
44
47
  if chr == config.wildcard
45
48
  0
@@ -48,6 +51,7 @@ module Packetman
48
51
  end.to_s(2).rjust(self.class.bit_density, '0')
49
52
  end
50
53
 
54
+ # Binary string for _chr_ substituting wildcards as necessary
51
55
  def bin_chr(chr)
52
56
  chr = '0' if chr == config.wildcard
53
57
 
@@ -67,6 +71,7 @@ module Packetman
67
71
  hex_encode(map_chr{ |c| bin_chr(c) })
68
72
  end
69
73
 
74
+ # Transform _bin_str_ to array of 32, 16, and 8 bit hex encoded strings
70
75
  def hex_encode(bin_str)
71
76
  bin_str.reverse.scan(/.{1,4}/).map{ |chunk|
72
77
  chunk.reverse.to_i(2).to_s(16)
@@ -75,14 +80,18 @@ module Packetman
75
80
  }
76
81
  end
77
82
 
78
- def to_s
79
- offset = 0
83
+ def clauses
84
+ start_bit = 0
80
85
  [].tap do |filter|
81
86
  search_hex.zip(mask_hex).each do |search, mask|
82
- filter << Packetman::Clause.new(search, mask, offset)
83
- offset += self.class.bit_length(search)
87
+ filter << Packetman::Clause.new(search, mask, start_bit)
88
+ start_bit += self.class.bit_length(search)
84
89
  end
85
- end.map{ |b| b.to_s }.join(' && ')
90
+ end
91
+ end
92
+
93
+ def to_s
94
+ clauses.map{ |clause| clause.to_s }.join(' && ')
86
95
  end
87
96
 
88
97
  end
@@ -1,57 +1,28 @@
1
+ require 'terminal-table'
2
+
1
3
  module Packetman
2
4
  class Table
3
5
  include ConfigMethods
4
6
 
5
- attr_reader :line_h, :line_v
6
- attr_accessor :columns
7
-
8
- def initialize(cols = 32)
9
- @line_v = '|'
10
- @line_h = '-'
11
- @columns = cols
12
- end
13
-
14
- def line_h=(value)
15
- raise "Invalid character" if value.length != 1
16
- @line_h = value
17
- end
18
-
19
- def line_v=(value)
20
- raise "Invalid character" if value.length != 1
21
- @line_v = value
22
- end
23
-
24
- def column_width
25
- (columns-1).to_s.length
26
- end
7
+ def initialize
27
8
 
28
- def horizontal_bar
29
- line_v + line_h*(table_width - 2) + line_v + "\n"
9
+ @term_table = Terminal::Table.new(headings: headings, rows: rows, style: style)
30
10
  end
31
11
 
32
- def table_width
33
- columns*(column_width + 1) + 1
12
+ def headings
13
+ [*0..31].map{ |c| "%02d" % c }
34
14
  end
35
15
 
36
- def cell_size(field_size)
37
- field_size*(column_width + 1) - 1
16
+ def rows
17
+ protocols[config.transport]['table']
38
18
  end
39
19
 
40
- def header_row
41
- line_v + columns.times.map{ |n| sprintf "%0#{column_width}d", n }.join(line_v) + line_v + "\n"
20
+ def style
21
+ { alignment: :center, padding_left: 0, padding_right: 0}
42
22
  end
43
23
 
44
24
  def to_s
45
- output = horizontal_bar + header_row + horizontal_bar
46
-
47
- protocols[config.transport]['table'].each do |label, size|
48
- output += sprintf "%s%.#{cell_size(size)}s", line_v, label.center(cell_size(size))
49
- if output.split("\n").last.length == (table_width - 1)
50
- output += line_v + "\n"
51
- output += horizontal_bar
52
- end
53
- end
54
- output
25
+ @term_table.to_s
55
26
  end
56
27
 
57
28
  end
@@ -1,3 +1,3 @@
1
1
  module Packetman
2
- VERSION = "0.1.2"
2
+ VERSION = "0.1.3"
3
3
  end
@@ -19,6 +19,8 @@ Gem::Specification.new do |spec|
19
19
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
20
  spec.require_paths = ["lib"]
21
21
 
22
+ spec.add_dependency "terminal-table", "~> 1.5.2"
23
+
22
24
  spec.add_development_dependency "bundler", "~> 1"
23
25
  spec.add_development_dependency "rake", "~> 10.0"
24
26
  spec.add_development_dependency "rspec", "~> 3.3"
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: packetman
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jason Scholl
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2015-11-28 00:00:00.000000000 Z
11
+ date: 2015-11-30 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: terminal-table
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.5.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.5.2
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: bundler
15
29
  requirement: !ruby/object:Gem::Requirement