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