packetgen 0.1.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b95374a3ba00773c1a45e0464a797b1173aa4285
4
+ data.tar.gz: 4a6e2f8b12b6552de7afdca695e99221453439c1
5
+ SHA512:
6
+ metadata.gz: 95ddd04f2758a1ef9f17d0b343ea9b00f718ad926adbf13bdd3bc9444e3d8a3cf980a8a8c7e3f5ebebf27a775da676f1754f86cf1dd15756c03fa76a1901184c
7
+ data.tar.gz: 145591b54094dcac9690e483138f123597029b12659c95d5ee1d7c14865f654003b17c24fd75a46647fe6c0864bccdff69bfd6e7e28623e2b3024628c447d073
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /vendor/
11
+ *~
12
+
@@ -0,0 +1,14 @@
1
+ language: ruby
2
+ sudo: required
3
+ rvm:
4
+ - 2.1
5
+ - 2.2
6
+ - 2.3.3
7
+
8
+ install:
9
+ - sudo apt-get update -qq
10
+ - sudo apt-get install libpcap-dev -qq
11
+ - bundler install --path vendor/bundle --jobs=3 --retry=3
12
+ script:
13
+ - bundler exec rake
14
+ - rvmsudo bundle exec rake spec:sudo
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in packetgen.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Sylvain Daubert
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,116 @@
1
+
2
+ [![Build Status](https://travis-ci.org/sdaubert/packetgen.svg?branch=master)](https://travis-ci.org/sdaubert/packetgen)
3
+
4
+ # PacketGen
5
+
6
+ PacketGen aims at generate and capture network packets easily.
7
+
8
+ ## Why PacketGen
9
+ Why create PacketGen ? There is already PacketFu!
10
+
11
+ Yes. But PacketFu is limited:
12
+ * upper protocols use fixed layers: TCP always uses IPv4, IP and IPv6 always uses Ethernet as MAC,...
13
+ * cannot handle tunneled packets (IP-in-IP, or deciphered ESP packets,...)
14
+ * cannot easily encapsulate or decapsulate packets
15
+ * parse packets top-down, and sometimes bad parse down layers
16
+ * cannot send packet on wire at IP/IPv6 level (Ethernet header is mandatory)
17
+
18
+ ## use cases
19
+
20
+ For now, PacketGen is only a concept...
21
+
22
+ ### Easily create packets
23
+ ```
24
+ PacketGen.gen('IP') # generate a IP packet object
25
+ PacketGen.gen('TCP') # generate a TCP over IP packet object
26
+ PacketGen.gen('IP').add('TCP') # the same
27
+ PacketGen.gen('Eth') # generate a Ethernet packet object
28
+ PacketGen.gen('IP').add('IP') # generate a IP-in-IP tunnel packet object
29
+
30
+ # Generate a IP packet object, specifying addresses
31
+ PacketGen.gen('IP', src: '192.168.1.1', dst: '192.168.1.2')
32
+
33
+ # get binary packet
34
+ PacketGen.gen('IP').to_s
35
+ ```
36
+
37
+ ### Send packets on wire
38
+ need PcapRub for Ethernet packets. Need a C extension (use of C socket API) for IP packets.
39
+
40
+ ```
41
+ # send Ethernet packet
42
+ PacketGen.gen('Eth', src: '00:00:00:00:01', dst: '00:00:00:00:02').to_w
43
+ # send IP packet
44
+ PacketGen.gen('IP', src: '192.168.1.1', dst: '192.168.1.2').to_w
45
+ # send forged IP packet over Ethernet
46
+ PacketGen.gen('Eth', src: '00:00:00:00:01', dst: '00:00:00:00:02').add('IP').to_w('eth1')
47
+ ```
48
+
49
+ ### Parse packets from binary data
50
+ ```
51
+ packet = PacketGen.parse(binary_data)
52
+ ```
53
+
54
+ ### Capture packets from wire
55
+ need PCapRub.
56
+
57
+ ```
58
+ # Capture packets, action from a block
59
+ PacketGen.capture('eth0') do |packet|
60
+ do_stuffs_with_packet
61
+ end
62
+
63
+ # Capture some packets, and act on them afterward
64
+ packets = PacketGen.capture('eth0', max: 10) # return when 10 packets were captured
65
+
66
+ # Use filters
67
+ packets = PacketGen.capture('eth0', filter: 'ip src 1.1.1.2', max: 1)
68
+ ```
69
+
70
+ ### Easily manipulate packets
71
+ ```
72
+ # access header fields
73
+ pkt = PacketGen.gen('IP').add('TCP')
74
+ pkt.ip.src = '192.168.1.1'
75
+ pkt.ip(src: '192.168.1.1', ttl: 4)
76
+ pkt.tcp.dport = 80
77
+
78
+ # access header fields when multiple header of one kind exist
79
+ pkt = PacketGen.gen('IP').add('IP')
80
+ pkt.ip.src = '192.168.1.1' # set outer src field
81
+ pkt.ip(2).src = '10.0.0.1' # set inner src field
82
+
83
+ # test packet types
84
+ pkt = PacketGen.gen('IP').add('TCP')
85
+ pkt.is? 'TCP' # => true
86
+ pkt.is? 'IP' # => true
87
+ pkt.is? 'UDP' # => false
88
+
89
+ # encapulsate/decapsulate packets
90
+ pkt2 = PacketGen.gen('IP').add('ESP', spi: 1234)
91
+ pkt.encap pkt2 # pkt is now a IP/ESP/IP/TCP packet
92
+ # eq. to pkt.encap('IP', 'ESP', esp_spi: 1234)
93
+ pkt.decap('IP', 'ESP') # pkt is now inner IP/TCP packet
94
+ ```
95
+
96
+ ### Read/write PcapNG files
97
+ ```
98
+ # read a PcapNG file, containing multiple packets
99
+ packets = PacketGen.read('file.pcapng')
100
+ packets.first.udp.sport = 65535
101
+ # write only one packet to a PcapNG file
102
+ pkt.write('one_packet.pcapng')
103
+ # write multiple packets to a PcapNG file
104
+ PacketGen.write('more_packets.pcapng', packets)
105
+ ```
106
+
107
+ ## License
108
+ MIT License (see [LICENSE](https://github.com/sdaubert/packetgen/LICENSE))
109
+
110
+ Copyright © 2016 Sylvain Daubert
111
+
112
+ ### Other sources
113
+ All original code maintains its copyright from its original authors and licensing.
114
+
115
+ This is mainly for StrucFu (copied from [PacketFu](https://github.com/packetfu/packetfu))
116
+ and PcapNG module (also copied from PacketFu, but I am the author).
@@ -0,0 +1,18 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'yard'
4
+
5
+ task :default => :spec
6
+
7
+ RSpec::Core::RakeTask.new do |t|
8
+ t.rspec_opts = '-t ~sudo'
9
+ end
10
+ RSpec::Core::RakeTask.new('spec:sudo') do |t|
11
+ t.rspec_opts = '-t sudo'
12
+ end
13
+
14
+ YARD::Rake::YardocTask.new do |t|
15
+ t.options = ['--no-private']
16
+ t.files = ['lib/**/*.rb', '-', 'LICENSE']
17
+ end
18
+
@@ -0,0 +1,83 @@
1
+ require 'packetgen/version'
2
+
3
+ # @author Sylvain Daubert
4
+ module PacketGen
5
+
6
+ # Base exception class for PacketGen exceptions
7
+ class Error < StandardError; end
8
+
9
+ # Packet badly formatted
10
+ class FormatError < Error; end
11
+
12
+ # Parsing error
13
+ class ParseError < Error; end
14
+
15
+ # Sending packet on wire error
16
+ class WireError < Error; end
17
+
18
+ # Shortcut for {Packet.gen}
19
+ # @param [String] protocol base protocol for packet
20
+ # @param [Hash] options specific options for +protocol+
21
+ # @return [Packet]
22
+ def self.gen(protocol, options={})
23
+ Packet.gen protocol, options
24
+ end
25
+
26
+ # Shortcut for {Packet.parse}
27
+ # @param [String] binary_str
28
+ # @param [String] first_header First protocol header
29
+ # @return [Packet]
30
+ def self.parse(binary_str, first_header: nil)
31
+ Packet.parse binary_str, first_header
32
+ end
33
+
34
+ # Shortcut for {Packet.capture}
35
+ # @param [String] iface interface name
36
+ # @param [Hash] options capture options. See {Packet.capture}.
37
+ # @yieldparam [Packet] packet
38
+ # @return [Array<Packet>]
39
+ def self.capture(iface, options={})
40
+ Packet.capture(iface, options) { |packet| yield packet }
41
+ end
42
+
43
+ # Shortcut for {Packet.read}
44
+ # @param [String] filename PcapNG file
45
+ # @return [Array<Packet>]
46
+ def self.read(filename)
47
+ Packet.read filename
48
+ end
49
+
50
+ # Shortcut for {Packet.write}
51
+ # @param [String] filename
52
+ # @param [Array<Packet>] packets packets to write
53
+ # @return [void]
54
+ def self.write(filename, packets)
55
+ Packet.write filename, packets
56
+ end
57
+
58
+ # Force binary encoding for +str+
59
+ # @param [String] str
60
+ # @return [String] binary encoded string
61
+ def self.force_binary(str)
62
+ str.force_encoding Encoding::BINARY
63
+ end
64
+
65
+ # Get default network interface (ie. first non-loopback declared interface)
66
+ # @return [String]
67
+ def self.default_iface
68
+ return @default_iface if @default_iface
69
+
70
+ ipaddr = `ip addr`.split("\n")
71
+ @default_iface = ipaddr.each_with_index do |line, i|
72
+ m = line.match(/^\d+: (\w+\d+):/)
73
+ next if m.nil?
74
+ next if m[1] == 'lo'
75
+ break m[1]
76
+ end
77
+ end
78
+ end
79
+
80
+ require 'packetgen/structfu'
81
+ require 'packetgen/packet'
82
+ require 'packetgen/capture'
83
+ require 'packetgen/pcapng'
@@ -0,0 +1,105 @@
1
+ module PacketGen
2
+
3
+ # Capture packets from wire
4
+ # @author Sylvain Daubert
5
+ class Capture
6
+
7
+ # Default snaplen to use if :snaplen option not defined
8
+ DEFAULT_SNAPLEN = 0xffff
9
+
10
+ # Get captured packets
11
+ # @return [Array<Packets>]
12
+ attr_reader :packets
13
+
14
+ # Get captured packet raw data
15
+ # @return [Array<String>]
16
+ attr_reader :raw_packets
17
+
18
+ # @param [String] iface interface on which capture packets
19
+ # @param [Hash] options
20
+ # @option options [Integer] :max maximum number of packets to capture
21
+ # @option options [Integer] :timeout maximum number of seconds before end
22
+ # of capture. Default: +nil+ (no timeout)
23
+ # @option options [String] :filter bpf filter
24
+ # @option options [Boolean] :promiscuous (default: +false+)
25
+ # @option options [Boolean] :parse parse raw data to generate packets before
26
+ # yielding. Default: +true+
27
+ # @option options [Integer] :snaplen maximum number of bytes to capture for
28
+ # each packet
29
+ def initialize(iface, options={})
30
+ @packets = []
31
+ @raw_packets = []
32
+ @iface = iface
33
+ set_options options
34
+ end
35
+
36
+ # Start capture
37
+ # @param [Hash] options complete see {#initialize}.
38
+ # @yieldparam [Packet,String] packet if a block is given, yield each
39
+ # captured packet (Packet or raw data String, depending on +:parse+)
40
+ def start(options={})
41
+ set_options options
42
+ @pcap = PCAPRUB::Pcap.open_live(@iface, @snaplen, @promisc, 1)
43
+ set_filter
44
+
45
+ @cap_thread = Thread.new do
46
+ @pcap.each do |packet_data|
47
+ @raw_packets << packet_data
48
+ if @parse
49
+ packet = Packet.parse(packet_data)
50
+ @packets << packet
51
+ yield packet if block_given?
52
+ else
53
+ yield packet_data if block_given?
54
+ end
55
+ if @max
56
+ break if @raw_packets.size >= @max
57
+ end
58
+ end
59
+ end
60
+ @cap_thread.join(@timeout)
61
+ end
62
+
63
+ # Stop capture. Should be used from another thread, as {#start} blocs.
64
+ #
65
+ # BEWARE: multiple capture should not be started in different threads. No effort
66
+ # has been made to make Capture nor PacketGen thread-safe.
67
+ # @return [void]
68
+ def stop
69
+ @cap_thread.kill
70
+ end
71
+
72
+ private
73
+
74
+ def set_options(options)
75
+ @max = options[:max] if options[:max]
76
+ @filter = options[:filter] if options[:filter]
77
+ if options[:timeout]
78
+ @timeout = options[:timeout]
79
+ else
80
+ @timeout ||= 0
81
+ end
82
+ if options[:promisc]
83
+ @promisc = options[:promisc]
84
+ else
85
+ @promisc ||= false
86
+ end
87
+ if options[:snaplen]
88
+ @snaplen = options[:snaplen]
89
+ else
90
+ @snaplen ||= DEFAULT_SNAPLEN
91
+ end
92
+ if options[:parse].nil?
93
+ @parse = true if @parse.nil?
94
+ else
95
+ @parse = options[:parse]
96
+ end
97
+ end
98
+
99
+ def set_filter
100
+ return if @filter.nil?
101
+ return if @filter.empty?
102
+ @pcap.setfilter @filter
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,21 @@
1
+ module PacketGen
2
+ # Namespace for protocol header classes
3
+ # @author Sylvain Daubert
4
+ module Header
5
+
6
+ # Get known header classes
7
+ # @return [Array<Class>]
8
+ def self.all
9
+ constants.map { |sym| const_get sym }.
10
+ select { |klass| klass < Struct && klass < HeaderMethods }
11
+ end
12
+ end
13
+ end
14
+
15
+ require_relative 'header/header_class_methods'
16
+ require_relative 'header/header_methods'
17
+ require_relative 'header/eth'
18
+ require_relative 'header/ip'
19
+ require_relative 'header/arp'
20
+ require_relative 'header/ipv6'
21
+ require_relative 'header/udp'
@@ -0,0 +1,148 @@
1
+ module PacketGen
2
+ module Header
3
+
4
+ # ARP header class
5
+ # @author Sylvain Daubert
6
+ class ARP < Struct.new(:hw_type, :proto, :hw_len, :proto_len, :opcode,
7
+ :src_mac, :src_ip, :dst_mac, :dst_ip, :body)
8
+ include StructFu
9
+ include HeaderMethods
10
+ extend HeaderClassMethods
11
+
12
+ # @param [Hash] options
13
+ # @option options [Integer] :hw_type network protocol type (default: 1)
14
+ # @option options [Integer] :proto internet protocol type (default: 0x800)
15
+ # @option options [Integer] :hw_len length of hardware addresses (default: 6)
16
+ # @option options [Integer] :proto_len length of internet addresses (default: 4)
17
+ # @option options [Integer] :opcode operation performing by sender (default: 1).
18
+ # known values are +request+ (1) and +reply+ (2)
19
+ # @option options [String] :src_mac sender hardware address
20
+ # @option options [String] :src_ip sender internet address
21
+ # @option options [String] :dst_mac target hardware address
22
+ # @option options [String] :dst_ip targetr internet address
23
+ def initialize(options={})
24
+ super Int16.new(options[:hw_type] || 1),
25
+ Int16.new(options[:proto] || 0x800),
26
+ Int8.new(options[:hw_len] || 6),
27
+ Int8.new(options[:proto_len] || 4),
28
+ Int16.new(options[:opcode] || 1),
29
+ Eth::MacAddr.new.parse(options[:src_mac]),
30
+ IP::Addr.new.parse(options[:src_ip]),
31
+ Eth::MacAddr.new.parse(options[:dst_mac]),
32
+ IP::Addr.new.parse(options[:dst_ip]),
33
+ StructFu::String.new.read(options[:body])
34
+ end
35
+
36
+ # Read a ARP header from a string
37
+ # @param [String] str binary string
38
+ # @return [self]
39
+ def read(str)
40
+ force_binary str
41
+ raise ParseError, 'string too short for ARP' if str.size < self.sz
42
+ self[:hw_type].read str[0, 2]
43
+ self[:proto].read str[2, 2]
44
+ self[:hw_len].read str[4, 1]
45
+ self[:proto_len].read str[5, 1]
46
+ self[:opcode].read str[6, 2]
47
+ self[:src_mac].read str[8, 6]
48
+ self[:src_ip].read str[14, 4]
49
+ self[:dst_mac].read str[18, 6]
50
+ self[:dst_ip].read str[24, 4]
51
+ self[:body].read str[28..-1]
52
+ end
53
+
54
+ # @!attribute [rw] hw_type
55
+ # @return [Integer]
56
+ def hw_type
57
+ self[:hw_type].to_i
58
+ end
59
+
60
+ def hw_type=(i)
61
+ self[:hw_type].read i
62
+ end
63
+
64
+ # @!attribute [rw] proto
65
+ # @return [Integer]
66
+ def proto
67
+ self[:proto].to_i
68
+ end
69
+
70
+ def proto=(i)
71
+ self[:proto].read i
72
+ end
73
+
74
+ # @!attribute [rw] hw_len
75
+ # @return [Integer]
76
+ def hw_len
77
+ self[:hw_len].to_i
78
+ end
79
+
80
+ def hw_len=(i)
81
+ self[:hw_len].read i
82
+ end
83
+
84
+ # @!attribute [rw] proto_len
85
+ # @return [Integer]
86
+ def proto_len
87
+ self[:proto_len].to_i
88
+ end
89
+
90
+ def proto_len=(i)
91
+ self[:proto_len].read i
92
+ end
93
+
94
+ # @!attribute [rw] opcode
95
+ # @return [Integer]
96
+ def opcode
97
+ self[:opcode].to_i
98
+ end
99
+
100
+ def opcode=(i)
101
+ self[:opcode].read i
102
+ end
103
+
104
+ # @!attribute [rw] src_mac
105
+ # @return [String]
106
+ def src_mac
107
+ self[:src_mac].to_x
108
+ end
109
+
110
+ def src_mac=(addr)
111
+ self[:src_mac].parse addr
112
+ end
113
+
114
+ # @!attribute [rw] src_ip
115
+ # @return [String]
116
+ def src_ip
117
+ self[:src_ip].to_x
118
+ end
119
+
120
+ def src_ip=(addr)
121
+ self[:src_ip].parse addr
122
+ end
123
+
124
+ # @!attribute [rw] dst_mac
125
+ # @return [String]
126
+ def dst_mac
127
+ self[:dst_mac].to_x
128
+ end
129
+
130
+ def dst_mac=(addr)
131
+ self[:dst_mac].parse addr
132
+ end
133
+
134
+ # @!attribute [rw] dst_ip
135
+ # @return [String]
136
+ def dst_ip
137
+ self[:dst_ip].to_x
138
+ end
139
+
140
+ def dst_ip=(addr)
141
+ self[:dst_ip].parse addr
142
+ end
143
+ end
144
+
145
+ Eth.bind_header ARP, proto: 0x806
146
+ end
147
+ end
148
+