packetgen 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.travis.yml +14 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +116 -0
- data/Rakefile +18 -0
- data/lib/packetgen.rb +83 -0
- data/lib/packetgen/capture.rb +105 -0
- data/lib/packetgen/header.rb +21 -0
- data/lib/packetgen/header/arp.rb +148 -0
- data/lib/packetgen/header/eth.rb +155 -0
- data/lib/packetgen/header/header_class_methods.rb +28 -0
- data/lib/packetgen/header/header_methods.rb +51 -0
- data/lib/packetgen/header/ip.rb +283 -0
- data/lib/packetgen/header/ipv6.rb +215 -0
- data/lib/packetgen/header/udp.rb +133 -0
- data/lib/packetgen/packet.rb +357 -0
- data/lib/packetgen/pcapng.rb +39 -0
- data/lib/packetgen/pcapng/block.rb +32 -0
- data/lib/packetgen/pcapng/epb.rb +131 -0
- data/lib/packetgen/pcapng/file.rb +345 -0
- data/lib/packetgen/pcapng/idb.rb +145 -0
- data/lib/packetgen/pcapng/shb.rb +173 -0
- data/lib/packetgen/pcapng/spb.rb +103 -0
- data/lib/packetgen/pcapng/unknown_block.rb +80 -0
- data/lib/packetgen/structfu.rb +357 -0
- data/lib/packetgen/version.rb +7 -0
- data/packetgen.gemspec +30 -0
- metadata +155 -0
@@ -0,0 +1,155 @@
|
|
1
|
+
module PacketGen
|
2
|
+
module Header
|
3
|
+
|
4
|
+
# Ethernet header class
|
5
|
+
# @author Sylvain Daubert
|
6
|
+
class Eth < Struct.new(:dst, :src, :proto, :body)
|
7
|
+
include StructFu
|
8
|
+
include HeaderMethods
|
9
|
+
extend HeaderClassMethods
|
10
|
+
|
11
|
+
# Ethernet MAC address, as a group of 6 bytes
|
12
|
+
# @author Sylvain Daubert
|
13
|
+
class MacAddr < Struct.new(:a0, :a1, :a2, :a3, :a4, :a5)
|
14
|
+
include StructFu
|
15
|
+
|
16
|
+
# @param [Hash] options
|
17
|
+
# @option options [Integer] :a0
|
18
|
+
# @option options [Integer] :a1
|
19
|
+
# @option options [Integer] :a2
|
20
|
+
# @option options [Integer] :a3
|
21
|
+
# @option options [Integer] :a4
|
22
|
+
# @option options [Integer] :a5
|
23
|
+
def initialize(options={})
|
24
|
+
super Int8.new(options[:a0]),
|
25
|
+
Int8.new(options[:a1]),
|
26
|
+
Int8.new(options[:a2]),
|
27
|
+
Int8.new(options[:a3]),
|
28
|
+
Int8.new(options[:a4]),
|
29
|
+
Int8.new(options[:a5])
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
# Parse a string to populate MacAddr
|
34
|
+
# @param [String] str
|
35
|
+
# @return [self]
|
36
|
+
def parse(str)
|
37
|
+
return self if str.nil?
|
38
|
+
bytes = str.split(/:/)
|
39
|
+
unless bytes.size == 6
|
40
|
+
raise ArgumentError, 'not a MAC address'
|
41
|
+
end
|
42
|
+
self[:a0].read(bytes[0].to_i(16))
|
43
|
+
self[:a1].read(bytes[1].to_i(16))
|
44
|
+
self[:a2].read(bytes[2].to_i(16))
|
45
|
+
self[:a3].read(bytes[3].to_i(16))
|
46
|
+
self[:a4].read(bytes[4].to_i(16))
|
47
|
+
self[:a5].read(bytes[5].to_i(16))
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
# Read a MacAddr from a string
|
52
|
+
# @param [String] str binary string
|
53
|
+
# @return [self]
|
54
|
+
def read(str)
|
55
|
+
return self if str.nil?
|
56
|
+
raise ParseError, 'string too short for Eth' if str.size < self.sz
|
57
|
+
force_binary str
|
58
|
+
[:a0, :a1, :a2, :a3, :a4, :a5].each_with_index do |byte, i|
|
59
|
+
self[byte].read str[i, 1]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
[:a0, :a1, :a2, :a3, :a4, :a5].each do |sym|
|
64
|
+
class_eval "def #{sym}; self[:#{sym}].to_i; end\n" \
|
65
|
+
"def #{sym}=(v); self[:#{sym}].read v; end"
|
66
|
+
end
|
67
|
+
|
68
|
+
# Addr in human readable form (dotted format)
|
69
|
+
# @return [String]
|
70
|
+
def to_x
|
71
|
+
members.map { |m| "#{'%02x' % self[m]}" }.join(':')
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# @private snap length for PCAPRUB
|
76
|
+
PCAP_SNAPLEN = 0xffff
|
77
|
+
# @private promiscuous (or not) for PCAPRUB
|
78
|
+
PCAP_PROMISC = false
|
79
|
+
# @private timeout for PCAPRUB
|
80
|
+
PCAP_TIMEOUT = 1
|
81
|
+
|
82
|
+
# @param [Hash] options
|
83
|
+
# @option options [String] :dst MAC destination address
|
84
|
+
# @option options [String] :src MAC source address
|
85
|
+
# @option options [Integer] :proto
|
86
|
+
def initialize(options={})
|
87
|
+
super MacAddr.new.parse(options[:dst] || '00:00:00:00:00:00'),
|
88
|
+
MacAddr.new.parse(options[:src] || '00:00:00:00:00:00'),
|
89
|
+
Int16.new(options[:proto] || 0),
|
90
|
+
StructFu::String.new.read(options[:body])
|
91
|
+
end
|
92
|
+
|
93
|
+
# Read a Eth header from a string
|
94
|
+
# @param [String] str binary string
|
95
|
+
# @return [self]
|
96
|
+
def read(str)
|
97
|
+
return self if str.nil?
|
98
|
+
raise ParseError, 'string too short for Eth' if str.size < self.sz
|
99
|
+
force_binary str
|
100
|
+
self[:dst].read str[0, 6]
|
101
|
+
self[:src].read str[6, 6]
|
102
|
+
self[:proto].read str[12, 2]
|
103
|
+
self[:body].read str[14..-1]
|
104
|
+
self
|
105
|
+
end
|
106
|
+
|
107
|
+
# Get MAC destination address
|
108
|
+
# @return [String]
|
109
|
+
def dst
|
110
|
+
self[:dst].to_x
|
111
|
+
end
|
112
|
+
|
113
|
+
# Set MAC destination address
|
114
|
+
# @param [String] addr
|
115
|
+
# @return [String]
|
116
|
+
def dst=(addr)
|
117
|
+
self[:dst].parse addr
|
118
|
+
end
|
119
|
+
|
120
|
+
# Get MAC source address
|
121
|
+
# @return [String]
|
122
|
+
def src
|
123
|
+
self[:src].to_x
|
124
|
+
end
|
125
|
+
|
126
|
+
# Set MAC source address
|
127
|
+
# @param [String] addr
|
128
|
+
# @return [String]
|
129
|
+
def src=(addr)
|
130
|
+
self[:src].parse addr
|
131
|
+
end
|
132
|
+
|
133
|
+
# Get protocol field
|
134
|
+
# @return [Integer]
|
135
|
+
def proto
|
136
|
+
self[:proto].to_i
|
137
|
+
end
|
138
|
+
|
139
|
+
# Set protocol field
|
140
|
+
# @param [Integer] proto
|
141
|
+
# @return [Integer]
|
142
|
+
def proto=(proto)
|
143
|
+
self[:proto].value = proto
|
144
|
+
end
|
145
|
+
|
146
|
+
# send Eth packet on wire.
|
147
|
+
# @param [String] iface interface name
|
148
|
+
# @return [void]
|
149
|
+
def to_w(iface)
|
150
|
+
pcap = PCAPRUB::Pcap.open_live(iface, PCAP_SNAPLEN, PCAP_PROMISC, PCAP_TIMEOUT)
|
151
|
+
pcap.inject self.to_s
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module PacketGen
|
2
|
+
module Header
|
3
|
+
|
4
|
+
module HeaderClassMethods
|
5
|
+
|
6
|
+
# Simple class to handle header association
|
7
|
+
Binding = Struct.new(:key, :value)
|
8
|
+
|
9
|
+
# Bind a upper header to current class
|
10
|
+
# @param [Class] header_klass header class to bind to current class
|
11
|
+
# @param [Hash] args current class field and its value when +header_klass+
|
12
|
+
# is embedded in current class
|
13
|
+
# @return [void]
|
14
|
+
def bind_header(header_klass, args={})
|
15
|
+
@known_headers ||= {}
|
16
|
+
key = args.keys.first
|
17
|
+
@known_headers[header_klass] = Binding.new(key, args[key])
|
18
|
+
end
|
19
|
+
|
20
|
+
# Get knwon headers
|
21
|
+
# @return [Hash] keys: header classes, values: struct with methods #key and #value
|
22
|
+
def known_headers
|
23
|
+
@known_headers ||= {}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module PacketGen
|
2
|
+
module Header
|
3
|
+
|
4
|
+
# Mixin for various headers
|
5
|
+
# @author Sylvain Daubert
|
6
|
+
module HeaderMethods
|
7
|
+
|
8
|
+
# @api private
|
9
|
+
# Set reference of packet which owns this header
|
10
|
+
# @param [Packet] packet
|
11
|
+
# @return [void]
|
12
|
+
def packet=(packet)
|
13
|
+
@packet = packet
|
14
|
+
end
|
15
|
+
|
16
|
+
# @api private
|
17
|
+
# Get rference on packet which owns this header
|
18
|
+
# @return [Packet]
|
19
|
+
def packet
|
20
|
+
@packet
|
21
|
+
end
|
22
|
+
|
23
|
+
# @api private
|
24
|
+
# Get +header+ id in packet headers array
|
25
|
+
# @param [Header] header
|
26
|
+
# @return [Integer]
|
27
|
+
# @raise FormatError +header+ not in a packet
|
28
|
+
def header_id(header)
|
29
|
+
raise FormatError, "header of type #{header.class} not in a packet" if packet.nil?
|
30
|
+
id = packet.headers.index(header)
|
31
|
+
if id.nil?
|
32
|
+
raise FormatError, "header of type #{header.class} not in packet #{packet}"
|
33
|
+
end
|
34
|
+
id
|
35
|
+
end
|
36
|
+
|
37
|
+
# @api private
|
38
|
+
# Get IP or IPv6 previous header from +header+
|
39
|
+
# @param [Header] header
|
40
|
+
# @return [Header]
|
41
|
+
# @raise FormatError no IP or IPv6 header previous +header+ in packet
|
42
|
+
# @raise FormatError +header+ not in a packet
|
43
|
+
def ip_header(header)
|
44
|
+
hid = header_id(header)
|
45
|
+
iph = packet.headers[0...hid].reverse.find { |h| h.is_a? IP }
|
46
|
+
raise FormatError, 'no IP or IPv6 header in packet' if iph.nil?
|
47
|
+
iph
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,283 @@
|
|
1
|
+
module PacketGen
|
2
|
+
module Header
|
3
|
+
|
4
|
+
# IP header class
|
5
|
+
# @author Sylvain Daubert
|
6
|
+
class IP < Struct.new(:version, :ihl, :tos, :length, :id, :frag, :ttl,
|
7
|
+
:proto,:sum, :src, :dst, :body)
|
8
|
+
include StructFu
|
9
|
+
include HeaderMethods
|
10
|
+
extend HeaderClassMethods
|
11
|
+
|
12
|
+
# IP address, as a group of 4 bytes
|
13
|
+
# @author Sylvain Daubert
|
14
|
+
class Addr < Struct.new(:a1, :a2, :a3, :a4)
|
15
|
+
include StructFu
|
16
|
+
|
17
|
+
IPV4_ADDR_REGEX = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/
|
18
|
+
|
19
|
+
# @param [Hash] options
|
20
|
+
# @option options [Integer] :a1
|
21
|
+
# @option options [Integer] :a2
|
22
|
+
# @option options [Integer] :a3
|
23
|
+
# @option options [Integer] :a4
|
24
|
+
def initialize(options={})
|
25
|
+
super Int8.new(options[:a1]),
|
26
|
+
Int8.new(options[:a2]),
|
27
|
+
Int8.new(options[:a3]),
|
28
|
+
Int8.new(options[:a4])
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
# Parse a dotted address
|
33
|
+
# @param [String] str
|
34
|
+
# @return [self]
|
35
|
+
def parse(str)
|
36
|
+
return self if str.nil?
|
37
|
+
m = str.match(IPV4_ADDR_REGEX)
|
38
|
+
if m
|
39
|
+
self[:a1].read m[1].to_i
|
40
|
+
self[:a2].read m[2].to_i
|
41
|
+
self[:a3].read m[3].to_i
|
42
|
+
self[:a4].read m[4].to_i
|
43
|
+
end
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
# Read a Addr from a string
|
48
|
+
# @param [String] str binary string
|
49
|
+
# @return [self]
|
50
|
+
def read(str)
|
51
|
+
return self if str.nil?
|
52
|
+
raise ParseError, 'string too short for Eth' if str.size < self.sz
|
53
|
+
force_binary str
|
54
|
+
[:a1, :a2, :a3, :a4].each_with_index do |byte, i|
|
55
|
+
self[byte].read str[i, 1]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
[:a1, :a2, :a3, :a4].each do |sym|
|
60
|
+
class_eval "def #{sym}; self[:#{sym}].to_i; end\n" \
|
61
|
+
"def #{sym}=(v); self[:#{sym}].read v; end"
|
62
|
+
end
|
63
|
+
|
64
|
+
# Addr in human readable form (dotted format)
|
65
|
+
# @return [String]
|
66
|
+
def to_x
|
67
|
+
members.map { |m| "#{self[m].to_i}" }.join('.')
|
68
|
+
end
|
69
|
+
|
70
|
+
# Addr as an integer
|
71
|
+
# @return [Integer]
|
72
|
+
def to_i
|
73
|
+
(self.a1 << 24) | (self.a2 << 16) | (self.a3 << 8) |
|
74
|
+
self.a4
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# @param [Hash] options
|
79
|
+
# @option options [Integer] :version
|
80
|
+
# @option options [Integer] :ihl this header size in 4-byte words
|
81
|
+
# @option options [Integer] :tos
|
82
|
+
# @option options [Integer] :length IP packet length, including this header
|
83
|
+
# @option options [Integer] :id
|
84
|
+
# @option options [Integer] :frag
|
85
|
+
# @option options [Integer] :ttl
|
86
|
+
# @option options [Integer] :proto
|
87
|
+
# @option options [Integer] :sum IP header checksum
|
88
|
+
# @option options [String] :src IP source dotted address
|
89
|
+
# @option options [String] :dst IP destination dotted address
|
90
|
+
def initialize(options={})
|
91
|
+
super options[:version] || 4,
|
92
|
+
options[:ihl] || 5,
|
93
|
+
Int8.new(options[:tos] || 0),
|
94
|
+
Int16.new(options[:length] || 20),
|
95
|
+
Int16.new(options[:id] || rand(65535)),
|
96
|
+
Int16.new(options[:frag] || 0),
|
97
|
+
Int8.new(options[:ttl] || 64),
|
98
|
+
Int8.new(options[:proto]),
|
99
|
+
Int16.new(options[:sum] || 0),
|
100
|
+
Addr.new.parse(options[:src] || '127.0.0.1'),
|
101
|
+
Addr.new.parse(options[:dst] || '127.0.0.1'),
|
102
|
+
StructFu::String.new.read(options[:body])
|
103
|
+
end
|
104
|
+
|
105
|
+
# Read a IP header from a string
|
106
|
+
# @param [String] str binary string
|
107
|
+
# @return [self]
|
108
|
+
def read(str)
|
109
|
+
return self if str.nil?
|
110
|
+
raise ParseError, 'string too short for Eth' if str.size < self.sz
|
111
|
+
force_binary str
|
112
|
+
vihl = str[0, 1].unpack('C').first
|
113
|
+
self[:version] = vihl >> 4
|
114
|
+
self[:ihl] = vihl & 0x0f
|
115
|
+
self[:tos].read str[1, 1]
|
116
|
+
self[:length].read str[2, 2]
|
117
|
+
self[:id].read str[4, 2]
|
118
|
+
self[:frag].read str[6, 2]
|
119
|
+
self[:ttl].read str[8, 1]
|
120
|
+
self[:proto].read str[9, 1]
|
121
|
+
self[:sum].read str[10, 2]
|
122
|
+
self[:src].read str[12, 4]
|
123
|
+
self[:dst].read str[16, 4]
|
124
|
+
self[:body].read str[20..-1]
|
125
|
+
self
|
126
|
+
end
|
127
|
+
|
128
|
+
# Compute checksum and set +sum+ field
|
129
|
+
# @return [Integer]
|
130
|
+
def calc_sum
|
131
|
+
checksum = (self.version << 12) | (self.ihl << 8) | self.tos
|
132
|
+
checksum += self.length
|
133
|
+
checksum += self.id
|
134
|
+
checksum += self.frag
|
135
|
+
checksum += (self.ttl << 8) | self.proto
|
136
|
+
checksum += (self[:src].to_i >> 16)
|
137
|
+
checksum += (self[:src].to_i & 0xffff)
|
138
|
+
checksum += self[:dst].to_i >> 16
|
139
|
+
checksum += self[:dst].to_i & 0xffff
|
140
|
+
checksum = (checksum & 0xffff) + (checksum >> 16)
|
141
|
+
checksum = ~(checksum % 0xffff ) & 0xffff
|
142
|
+
self[:sum].value = (checksum == 0) ? 0xffff : checksum
|
143
|
+
end
|
144
|
+
|
145
|
+
# Compute length and set +length+ field
|
146
|
+
# @return [Integer]
|
147
|
+
def calc_length
|
148
|
+
self[:length].value = self.sz
|
149
|
+
end
|
150
|
+
|
151
|
+
# Getter for TOS attribute
|
152
|
+
# @return [Integer]
|
153
|
+
def tos
|
154
|
+
self[:tos].to_i
|
155
|
+
end
|
156
|
+
|
157
|
+
# Setter for TOS attribute
|
158
|
+
# @param [Integer] tos
|
159
|
+
# @return [Integer]
|
160
|
+
def tos=(tos)
|
161
|
+
self[:tos].value = tos
|
162
|
+
end
|
163
|
+
|
164
|
+
# Getter for length attribute
|
165
|
+
# @return [Integer]
|
166
|
+
def length
|
167
|
+
self[:length].to_i
|
168
|
+
end
|
169
|
+
|
170
|
+
# Setter for length attribute
|
171
|
+
# @param [Integer] length
|
172
|
+
# @return [Integer]
|
173
|
+
def length=(length)
|
174
|
+
self[:length].value = length
|
175
|
+
end
|
176
|
+
|
177
|
+
# Getter for id attribute
|
178
|
+
# @return [Integer]
|
179
|
+
def id
|
180
|
+
self[:id].to_i
|
181
|
+
end
|
182
|
+
|
183
|
+
# Setter for id attribute
|
184
|
+
# @param [Integer] id
|
185
|
+
# @return [Integer]
|
186
|
+
def id=(id)
|
187
|
+
self[:id].value = id
|
188
|
+
end
|
189
|
+
|
190
|
+
# Getter for frag attribute
|
191
|
+
# @return [Integer]
|
192
|
+
def frag
|
193
|
+
self[:frag].to_i
|
194
|
+
end
|
195
|
+
|
196
|
+
# Setter for frag attribute
|
197
|
+
# @param [Integer] frag
|
198
|
+
# @return [Integer]
|
199
|
+
def frag=(frag)
|
200
|
+
self[:frag].value = frag
|
201
|
+
end
|
202
|
+
|
203
|
+
# Getter for ttl attribute
|
204
|
+
# @return [Integer]
|
205
|
+
def ttl
|
206
|
+
self[:ttl].to_i
|
207
|
+
end
|
208
|
+
|
209
|
+
# Setter for ttl attribute
|
210
|
+
# @param [Integer] ttl
|
211
|
+
# @return [Integer]
|
212
|
+
def ttl=(ttl)
|
213
|
+
self[:ttl].value = ttl
|
214
|
+
end
|
215
|
+
|
216
|
+
# Getter for proto attribute
|
217
|
+
# @return [Integer]
|
218
|
+
def proto
|
219
|
+
self[:proto].to_i
|
220
|
+
end
|
221
|
+
|
222
|
+
# Setter for proto attribute
|
223
|
+
# @param [Integer] proto
|
224
|
+
# @return [Integer]
|
225
|
+
def proto=(proto)
|
226
|
+
self[:proto].value = proto
|
227
|
+
end
|
228
|
+
|
229
|
+
# Getter for sum attribute
|
230
|
+
# @return [Integer]
|
231
|
+
def sum
|
232
|
+
self[:sum].to_i
|
233
|
+
end
|
234
|
+
|
235
|
+
# Setter for sum attribute
|
236
|
+
# @param [Integer] sum
|
237
|
+
# @return [Integer]
|
238
|
+
def sum=(sum)
|
239
|
+
self[:sum].value = sum
|
240
|
+
end
|
241
|
+
|
242
|
+
# Get IP source address
|
243
|
+
# @return [String] dotted address
|
244
|
+
def src
|
245
|
+
self[:src].to_x
|
246
|
+
end
|
247
|
+
alias :source :src
|
248
|
+
|
249
|
+
# Set IP source address
|
250
|
+
# @param [String] addr dotted IP address
|
251
|
+
# @return [String]
|
252
|
+
def src=(addr)
|
253
|
+
self[:src].parse addr
|
254
|
+
end
|
255
|
+
alias :source= :src=
|
256
|
+
|
257
|
+
# Get IP destination address
|
258
|
+
# @return [String] dotted address
|
259
|
+
def dst
|
260
|
+
self[:dst].to_x
|
261
|
+
end
|
262
|
+
alias :destination :dst
|
263
|
+
|
264
|
+
# Set IP destination address
|
265
|
+
# @param [String] addr dotted IP address
|
266
|
+
# @return [String]
|
267
|
+
def dst=(addr)
|
268
|
+
self[:dst].parse addr
|
269
|
+
end
|
270
|
+
alias :destination= :dst=
|
271
|
+
|
272
|
+
# Get binary string
|
273
|
+
# @return [String]
|
274
|
+
def to_s
|
275
|
+
first_byte = [(version << 4) | ihl].pack('C')
|
276
|
+
first_byte << to_a[2..-1].map { |field| field.to_s }.join
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
Eth.bind_header IP, proto: 0x800
|
281
|
+
IP.bind_header IP, proto: 4
|
282
|
+
end
|
283
|
+
end
|