packetgen 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+