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,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