packetgen 0.1.0

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