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,215 @@
|
|
1
|
+
require 'ipaddr'
|
2
|
+
|
3
|
+
module PacketGen
|
4
|
+
module Header
|
5
|
+
|
6
|
+
# IPv6 header class
|
7
|
+
# @author Sylvain Daubert
|
8
|
+
class IPv6 < Struct.new(:version, :traffic_class, :flow_label, :length,
|
9
|
+
:next, :hop, :src, :dst, :body)
|
10
|
+
include StructFu
|
11
|
+
include HeaderMethods
|
12
|
+
extend HeaderClassMethods
|
13
|
+
|
14
|
+
# IPv6 address, as a group of 8 2-byte words
|
15
|
+
# @author Sylvain Daubert
|
16
|
+
class Addr < Struct.new(:a1, :a2, :a3, :a4, :a5, :a6, :a7, :a8)
|
17
|
+
include StructFu
|
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
|
+
# @option options [Integer] :a5
|
25
|
+
# @option options [Integer] :a6
|
26
|
+
# @option options [Integer] :a7
|
27
|
+
# @option options [Integer] :a8
|
28
|
+
def initialize(options={})
|
29
|
+
super Int16.new(options[:a1]),
|
30
|
+
Int16.new(options[:a2]),
|
31
|
+
Int16.new(options[:a3]),
|
32
|
+
Int16.new(options[:a4]),
|
33
|
+
Int16.new(options[:a5]),
|
34
|
+
Int16.new(options[:a6]),
|
35
|
+
Int16.new(options[:a7]),
|
36
|
+
Int16.new(options[:a8])
|
37
|
+
end
|
38
|
+
|
39
|
+
# Parse a colon-delimited address
|
40
|
+
# @param [String] str
|
41
|
+
# @return [self]
|
42
|
+
def parse(str)
|
43
|
+
return self if str.nil?
|
44
|
+
addr = IPAddr.new(str)
|
45
|
+
raise ArgumentError, 'string is not a IPv6 address' unless addr.ipv6?
|
46
|
+
addri = addr.to_i
|
47
|
+
self.a1 = addri >> 112
|
48
|
+
self.a2 = addri >> 96 & 0xffff
|
49
|
+
self.a3 = addri >> 80 & 0xffff
|
50
|
+
self.a4 = addri >> 64 & 0xffff
|
51
|
+
self.a5 = addri >> 48 & 0xffff
|
52
|
+
self.a6 = addri >> 32 & 0xffff
|
53
|
+
self.a7 = addri >> 16 & 0xffff
|
54
|
+
self.a8 = addri & 0xffff
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
# Read a Addr6 from a binary string
|
59
|
+
# @param [String] str
|
60
|
+
# @return [self]
|
61
|
+
def read(str)
|
62
|
+
force_binary str
|
63
|
+
self[:a1].read str[0, 2]
|
64
|
+
self[:a2].read str[2, 2]
|
65
|
+
self[:a3].read str[4, 2]
|
66
|
+
self[:a4].read str[6, 2]
|
67
|
+
self[:a5].read str[8, 2]
|
68
|
+
self[:a6].read str[10, 2]
|
69
|
+
self[:a7].read str[12, 2]
|
70
|
+
self[:a8].read str[14, 2]
|
71
|
+
self
|
72
|
+
end
|
73
|
+
|
74
|
+
%i(a1 a2 a3 a4 a5 a6 a7 a8).each do |sym|
|
75
|
+
class_eval "def #{sym}; self[:#{sym}].to_i; end\n" \
|
76
|
+
"def #{sym}=(v); self[:#{sym}].read v; end"
|
77
|
+
end
|
78
|
+
|
79
|
+
# Addr6 in human readable form (colon-delimited hex string)
|
80
|
+
# @return [String]
|
81
|
+
def to_x
|
82
|
+
IPAddr.new(to_a.map { |a| a.to_i.to_s(16) }.join(':')).to_s
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# @param [Hash] options
|
87
|
+
# @option options [Integer] :version
|
88
|
+
# @option options [Integer] :traffic_length
|
89
|
+
# @option options [Integer] :flow_label
|
90
|
+
# @option options [Integer] :length payload length
|
91
|
+
# @option options [Integer] :next
|
92
|
+
# @option options [Integer] :hop
|
93
|
+
# @option options [String] :src colon-delimited source address
|
94
|
+
# @option options [String] :dst colon-delimited destination address
|
95
|
+
# @option options [String] :body binary string
|
96
|
+
def initialize(options={})
|
97
|
+
super options[:version] || 6,
|
98
|
+
options[:traffic_class] || 0,
|
99
|
+
options[:flow_label] || 0,
|
100
|
+
Int16.new(options[:length]),
|
101
|
+
Int8.new(options[:next]),
|
102
|
+
Int8.new(options[:hop] || 64),
|
103
|
+
Addr.new.parse(options[:src] || '::1'),
|
104
|
+
Addr.new.parse(options[:dst] || '::1'),
|
105
|
+
StructFu::String.new.read(options[:body])
|
106
|
+
end
|
107
|
+
|
108
|
+
# Read a IP header from a string
|
109
|
+
# @param [String] str binary string
|
110
|
+
# @return [self]
|
111
|
+
def read(str)
|
112
|
+
return self if str.nil?
|
113
|
+
raise ParseError, 'string too short for Eth' if str.size < self.sz
|
114
|
+
force_binary str
|
115
|
+
first32 = str[0, 4].unpack('N').first
|
116
|
+
self.version = first32 >> 28
|
117
|
+
self.traffic_class = (first32 >> 20) & 0xff
|
118
|
+
self.flow_label = first32 & 0xfffff
|
119
|
+
|
120
|
+
self[:length].read str[4, 2]
|
121
|
+
self[:next].read str[6, 1]
|
122
|
+
self[:hop].read str[7, 1]
|
123
|
+
self[:src].read str[8, 16]
|
124
|
+
self[:dst].read str[24, 16]
|
125
|
+
self[:body].read str[40..-1]
|
126
|
+
self
|
127
|
+
end
|
128
|
+
|
129
|
+
# Compute length and set +len+ field
|
130
|
+
# @return [Integer]
|
131
|
+
def calc_length
|
132
|
+
self.length = body.length
|
133
|
+
end
|
134
|
+
|
135
|
+
# Getter for length attribute
|
136
|
+
# @return [Integer]
|
137
|
+
def length
|
138
|
+
self[:length].to_i
|
139
|
+
end
|
140
|
+
|
141
|
+
# Setter for length attribute
|
142
|
+
# @param [Integer] i
|
143
|
+
# @return [Integer]
|
144
|
+
def length=(i)
|
145
|
+
self[:length].read i
|
146
|
+
end
|
147
|
+
|
148
|
+
# Getter for next attribute
|
149
|
+
# @return [Integer]
|
150
|
+
def next
|
151
|
+
self[:next].to_i
|
152
|
+
end
|
153
|
+
|
154
|
+
# Setter for next attribute
|
155
|
+
# @param [Integer] i
|
156
|
+
# @return [Integer]
|
157
|
+
def next=(i)
|
158
|
+
self[:next].read i
|
159
|
+
end
|
160
|
+
|
161
|
+
# Getter for hop attribute
|
162
|
+
# @return [Integer]
|
163
|
+
def hop
|
164
|
+
self[:hop].to_i
|
165
|
+
end
|
166
|
+
|
167
|
+
# Setter for hop attribute
|
168
|
+
# @param [Integer] i
|
169
|
+
# @return [Integer]
|
170
|
+
def hop=(i)
|
171
|
+
self[:hop].read i
|
172
|
+
end
|
173
|
+
|
174
|
+
# Getter for src attribute
|
175
|
+
# @return [String]
|
176
|
+
def src
|
177
|
+
self[:src].to_x
|
178
|
+
end
|
179
|
+
alias :source :src
|
180
|
+
|
181
|
+
# Setter for src attribute
|
182
|
+
# @param [String] addr
|
183
|
+
# @return [Integer]
|
184
|
+
def src=(addr)
|
185
|
+
self[:src].parse addr
|
186
|
+
end
|
187
|
+
alias :source= :src=
|
188
|
+
|
189
|
+
# Getter for dst attribute
|
190
|
+
# @return [String]
|
191
|
+
def dst
|
192
|
+
self[:dst].to_x
|
193
|
+
end
|
194
|
+
alias :destination :dst
|
195
|
+
|
196
|
+
# Setter for dst attribute
|
197
|
+
# @param [String] addr
|
198
|
+
# @return [Integer]
|
199
|
+
def dst=(addr)
|
200
|
+
self[:dst].parse addr
|
201
|
+
end
|
202
|
+
alias :destination= :dst=
|
203
|
+
|
204
|
+
# Get binary string
|
205
|
+
# @return [String]
|
206
|
+
def to_s
|
207
|
+
first32 = (version << 28) | (traffic_class << 20) | flow_label
|
208
|
+
[first32].pack('N') << to_a[3..-1].map { |field| field.to_s }.join
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
Eth.bind_header IPv6, proto: 0x86DD
|
213
|
+
IP.bind_header IPv6, proto: 41 # 6to4
|
214
|
+
end
|
215
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module PacketGen
|
2
|
+
module Header
|
3
|
+
|
4
|
+
# UDP header class
|
5
|
+
# @author Sylvain Daubert
|
6
|
+
class UDP < Struct.new(:sport, :dport, :length, :sum, :body)
|
7
|
+
include StructFu
|
8
|
+
include HeaderMethods
|
9
|
+
extend HeaderClassMethods
|
10
|
+
|
11
|
+
# IP protocol number for UDP
|
12
|
+
IP_PROTOCOL = 17
|
13
|
+
|
14
|
+
# @param [Hash] options
|
15
|
+
# @option options [Integer] :sport source port
|
16
|
+
# @option options [Integer] :dport destination port
|
17
|
+
# @option options [Integer] :length UDP length. Default: calculated
|
18
|
+
# @option options [Integer] :sum. UDP checksum. Default: 0
|
19
|
+
def initialize(options={})
|
20
|
+
super Int16.new(options[:sport]),
|
21
|
+
Int16.new(options[:dport]),
|
22
|
+
Int16.new(options[:length]),
|
23
|
+
Int16.new(options[:sum]),
|
24
|
+
StructFu::String.new.read(options[:body])
|
25
|
+
unless options[:length]
|
26
|
+
calc_length
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Read a IP header from a string
|
31
|
+
# @param [String] str binary string
|
32
|
+
# @return [self]
|
33
|
+
def read(str)
|
34
|
+
return self if str.nil?
|
35
|
+
raise ParseError, 'string too short for Eth' if str.size < self.sz
|
36
|
+
force_binary str
|
37
|
+
self[:sport].read str[0, 2]
|
38
|
+
self[:dport].read str[2, 2]
|
39
|
+
self[:length].read str[4, 2]
|
40
|
+
self[:sum].read str[6, 2]
|
41
|
+
self[:body].read str[8..-1]
|
42
|
+
end
|
43
|
+
|
44
|
+
# Compute checksum and set +sum+ field
|
45
|
+
# @return [Integer]
|
46
|
+
def calc_sum
|
47
|
+
ip = ip_header(self)
|
48
|
+
sum = ip[:src].to_i >> 16
|
49
|
+
sum += ip[:src].to_i & 0xffff
|
50
|
+
sum += ip[:dst].to_i >> 16
|
51
|
+
sum += ip[:dst].to_i & 0xffff
|
52
|
+
sum += IP_PROTOCOL
|
53
|
+
sum += length
|
54
|
+
sum += sport
|
55
|
+
sum += dport
|
56
|
+
sum += length
|
57
|
+
payload = body.to_s
|
58
|
+
payload << "\x00" unless payload.size % 2 == 0
|
59
|
+
payload.unpack('n*').each { |x| sum += x }
|
60
|
+
|
61
|
+
while sum > 0xffff do
|
62
|
+
sum = (sum & 0xffff) + (sum >> 16)
|
63
|
+
end
|
64
|
+
sum = ~sum & 0xffff
|
65
|
+
self[:sum].value = (sum == 0) ? 0xffff : sum
|
66
|
+
end
|
67
|
+
|
68
|
+
# Compute length and set +length+ field
|
69
|
+
# @return [Integer]
|
70
|
+
def calc_length
|
71
|
+
self[:length].value = self.sz
|
72
|
+
end
|
73
|
+
|
74
|
+
# Getter for source port
|
75
|
+
# @return [Integer]
|
76
|
+
def sport
|
77
|
+
self[:sport].to_i
|
78
|
+
end
|
79
|
+
alias :source_port :sport
|
80
|
+
|
81
|
+
# Setter for source port
|
82
|
+
# @param [Integer] port
|
83
|
+
# @return [Integer]
|
84
|
+
def sport=(port)
|
85
|
+
self[:sport].read port
|
86
|
+
end
|
87
|
+
alias :source_port= :sport=
|
88
|
+
|
89
|
+
# Getter for destination port
|
90
|
+
# @return [Integer]
|
91
|
+
def dport
|
92
|
+
self[:dport].to_i
|
93
|
+
end
|
94
|
+
alias :destination_port :dport
|
95
|
+
|
96
|
+
# Setter for destination port
|
97
|
+
# @param [Integer] port
|
98
|
+
# @return [Integer]
|
99
|
+
def dport=(port)
|
100
|
+
self[:dport].read port
|
101
|
+
end
|
102
|
+
alias :destination_port= :dport=
|
103
|
+
|
104
|
+
# Getter for length attribuute
|
105
|
+
# @return [Integer]
|
106
|
+
def length
|
107
|
+
self[:length].to_i
|
108
|
+
end
|
109
|
+
|
110
|
+
# Setter for length attribuute
|
111
|
+
# @param [Integer] port
|
112
|
+
# @return [Integer]
|
113
|
+
def length=(len)
|
114
|
+
self[:length].read len
|
115
|
+
end
|
116
|
+
|
117
|
+
# Getter for sum attribuute
|
118
|
+
# @return [Integer]
|
119
|
+
def sum
|
120
|
+
self[:sum].to_i
|
121
|
+
end
|
122
|
+
|
123
|
+
# Setter for sum attribuute
|
124
|
+
# @param [Integer] sum
|
125
|
+
# @return [Integer]
|
126
|
+
def sum=(sum)
|
127
|
+
self[:sum].read sum
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
IP.bind_header UDP, proto: UDP::IP_PROTOCOL
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,357 @@
|
|
1
|
+
require 'pcaprub'
|
2
|
+
|
3
|
+
module PacketGen
|
4
|
+
|
5
|
+
# An object of type {Packet} handles a network packet. This packet may contain
|
6
|
+
# multiple protocol headers, starting from MAC layer or from Network (OSI) layer.
|
7
|
+
#
|
8
|
+
# Creating a packet is fairly simple:
|
9
|
+
# Packet.gen 'IP', src: '192.168.1.1', dst: '192.168.1.2'
|
10
|
+
#
|
11
|
+
# == Create a packet
|
12
|
+
# Packets may be hand-made or parsed from a binary string:
|
13
|
+
# Packet.gen('IP', src: '192.168.1.1', dst: '192.168.1.2').add('UDP', sport: 45000, dport: 23)
|
14
|
+
# Packet.parse(binary_string)
|
15
|
+
#
|
16
|
+
# == Access packet information
|
17
|
+
# pkt = Packet.gen('IP').add('UDP')
|
18
|
+
# # read information
|
19
|
+
# pkt.udp.sport
|
20
|
+
# pkt.ip.ttl
|
21
|
+
# # set information
|
22
|
+
# pkt.udp.dport = 2323
|
23
|
+
# pkt.ip.ttl = 1
|
24
|
+
# pkt.ip(ttl: 1, id: 1234)
|
25
|
+
#
|
26
|
+
# == Save a packet to a file
|
27
|
+
# pkt.write('file.pcapng')
|
28
|
+
#
|
29
|
+
# == Get packets
|
30
|
+
# Packets may be captured from wire:
|
31
|
+
# Packet.capture('eth0') do |packet|
|
32
|
+
# do_some_stuffs
|
33
|
+
# end
|
34
|
+
# packets = Packet.capture('eth0', max: 5) # get 5 packets
|
35
|
+
#
|
36
|
+
# Packets may also be read from a file:
|
37
|
+
# packets = Packet.read(file.pcapng)
|
38
|
+
#
|
39
|
+
# == Save packets to a file
|
40
|
+
# Packet.write 'file.pcapng', packets
|
41
|
+
class Packet
|
42
|
+
# @return [Array<Header::Base]
|
43
|
+
attr_reader :headers
|
44
|
+
|
45
|
+
# @private maximum number of characters on a line for INSPECT
|
46
|
+
INSPECT_MAX_WIDTH = 70
|
47
|
+
|
48
|
+
# Create a new Packet
|
49
|
+
# @param [String] protocol base protocol for packet
|
50
|
+
# @param [Hash] options specific options for +protocol+
|
51
|
+
# @return [Packet]
|
52
|
+
def self.gen(protocol, options={})
|
53
|
+
self.new.add protocol, options
|
54
|
+
end
|
55
|
+
|
56
|
+
# Parse a binary string and generate a Packet from it.
|
57
|
+
# # auto-detect first header
|
58
|
+
# Packet.parse str
|
59
|
+
# # force decoding a Ethernet header for first header
|
60
|
+
# Packet.parse str, first_header: 'Eth'
|
61
|
+
# @param [String] binary_str
|
62
|
+
# @param [String,nil] first_header First protocol header. +nil+ means discover it!
|
63
|
+
# @return [Packet]
|
64
|
+
# @raise [ArgumentError] +first_header+ is an unknown header
|
65
|
+
def self.parse(binary_str, first_header: nil)
|
66
|
+
pkt = new
|
67
|
+
|
68
|
+
if first_header.nil?
|
69
|
+
# No decoding forced for first header. Have to guess it!
|
70
|
+
Header.all.each do |hklass|
|
71
|
+
hdr = hklass.new
|
72
|
+
hdr.read binary_str
|
73
|
+
# First header is found when:
|
74
|
+
# * for one known header,
|
75
|
+
# * it exists a known binding with a upper header
|
76
|
+
hklass.known_headers.each do |nh, binding|
|
77
|
+
if hdr.send(binding.key) == binding.value
|
78
|
+
first_header = hklass.to_s.gsub(/.*::/, '')
|
79
|
+
break
|
80
|
+
end
|
81
|
+
end
|
82
|
+
break unless first_header.nil?
|
83
|
+
end
|
84
|
+
if first_header.nil?
|
85
|
+
raise ParseError, 'cannot identify first header in string'
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
pkt.add(first_header)
|
90
|
+
pkt.headers.last.read binary_str
|
91
|
+
|
92
|
+
# Decode upper headers recursively
|
93
|
+
decode_packet_bottom_up = true
|
94
|
+
while decode_packet_bottom_up do
|
95
|
+
last_known_hdr = pkt.headers.last
|
96
|
+
last_known_hdr.class.known_headers.each do |nh, binding|
|
97
|
+
if last_known_hdr.send(binding.key) == binding.value
|
98
|
+
str = last_known_hdr.body
|
99
|
+
pkt.add nh.to_s.gsub(/.*::/, '')
|
100
|
+
pkt.headers.last.read str
|
101
|
+
break
|
102
|
+
end
|
103
|
+
end
|
104
|
+
decode_packet_bottom_up = (pkt.headers.last != last_known_hdr)
|
105
|
+
end
|
106
|
+
|
107
|
+
pkt
|
108
|
+
end
|
109
|
+
|
110
|
+
# Capture packets from +iface+
|
111
|
+
# @param [String] iface interface name
|
112
|
+
# @param [Hash] options capture options
|
113
|
+
# @option options [Integer] :max maximum number of packets to capture
|
114
|
+
# @option options [Integer] :timeout maximum number of seconds before end
|
115
|
+
# of capture
|
116
|
+
# @option options [String] :filter bpf filter
|
117
|
+
# @option options [Boolean] :promiscuous
|
118
|
+
# @yieldparam [Packet] packet if a block is given, yield each captured packet
|
119
|
+
# @return [Array<Packet>] captured packet
|
120
|
+
def self.capture(iface, options={})
|
121
|
+
capture = Capture.new(iface, options)
|
122
|
+
if block_given?
|
123
|
+
capture.start { |packet| yield packet }
|
124
|
+
else
|
125
|
+
capture.start
|
126
|
+
end
|
127
|
+
capture.packets
|
128
|
+
end
|
129
|
+
|
130
|
+
# Read packets from +filename+.
|
131
|
+
#
|
132
|
+
# For more control, see {PcapNG::File}.
|
133
|
+
# @param [String] filename PcapNG file
|
134
|
+
# @return [Array<Packet>]
|
135
|
+
def self.read(filename)
|
136
|
+
PcapNG::File.new.read_packets filename
|
137
|
+
end
|
138
|
+
|
139
|
+
# Write packets to +filename+
|
140
|
+
#
|
141
|
+
# For more options, see {PcapNG::File}.
|
142
|
+
# @param [String] filename
|
143
|
+
# @param [Array<Packet>] packets packets to write
|
144
|
+
# @return [void]
|
145
|
+
def self.write(filename, packets)
|
146
|
+
pf = PcapNG::File.new
|
147
|
+
pf.array_to_file packets
|
148
|
+
pf.to_f filename
|
149
|
+
end
|
150
|
+
|
151
|
+
# @private
|
152
|
+
def initialize
|
153
|
+
@headers = []
|
154
|
+
end
|
155
|
+
|
156
|
+
# Add a protocol on packet stack
|
157
|
+
# @param [String] protocol
|
158
|
+
# @param [Hash] options protocol specific options
|
159
|
+
# @return [self]
|
160
|
+
# @raise [ArgumentError] unknown protocol
|
161
|
+
def add(protocol, options={})
|
162
|
+
klass = check_protocol(protocol)
|
163
|
+
|
164
|
+
header = klass.new(options)
|
165
|
+
prev_header = @headers.last
|
166
|
+
if prev_header
|
167
|
+
binding = prev_header.class.known_headers[klass]
|
168
|
+
if binding.nil?
|
169
|
+
msg = "#{prev_header.class} knowns no layer association with #{protocol}. "
|
170
|
+
msg << "Try #{prev_header.class}.bind_layer(PacketGen::Header::#{protocol}, "
|
171
|
+
msg << "#{prev_header.class.to_s.gsub(/(.*)::/, '').downcase}_proto_field: "
|
172
|
+
msg << "value_for_#{protocol.downcase})"
|
173
|
+
raise ArgumentError, msg
|
174
|
+
end
|
175
|
+
prev_header[binding.key].read binding.value
|
176
|
+
prev_header.body = header
|
177
|
+
end
|
178
|
+
header.packet = self
|
179
|
+
@headers << header
|
180
|
+
unless respond_to? protocol.downcase
|
181
|
+
self.class.class_eval "def #{protocol.downcase}(arg=nil);" \
|
182
|
+
"header('#{protocol}', arg); end"
|
183
|
+
end
|
184
|
+
self
|
185
|
+
end
|
186
|
+
|
187
|
+
# Check if a protocol header is embedded in packet
|
188
|
+
# @return [Boolean]
|
189
|
+
# @raise [ArgumentError] unknown protocol
|
190
|
+
def is?(protocol)
|
191
|
+
klass = check_protocol protocol
|
192
|
+
@headers.any? { |h| h.is_a? klass }
|
193
|
+
end
|
194
|
+
|
195
|
+
# Recalculate all packet checksums
|
196
|
+
# @return [void]
|
197
|
+
def calc_sum
|
198
|
+
@headers.reverse.each do |header|
|
199
|
+
header.calc_sum if header.respond_to? :calc_sum
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
# Recalculate all packet length fields
|
204
|
+
# @return [void]
|
205
|
+
def calc_length
|
206
|
+
@headers.each do |header|
|
207
|
+
header.calc_length if header.respond_to? :calc_length
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
# Recalculate all calculatable fields (for now: length and sum)
|
212
|
+
# @return [void]
|
213
|
+
def calc
|
214
|
+
calc_sum
|
215
|
+
calc_length
|
216
|
+
end
|
217
|
+
|
218
|
+
# Get packet body
|
219
|
+
# @return [StructFu]
|
220
|
+
def body
|
221
|
+
@headers.last.body
|
222
|
+
end
|
223
|
+
|
224
|
+
# Set packet body
|
225
|
+
# @param [String]
|
226
|
+
# @return [void]
|
227
|
+
def body=(str)
|
228
|
+
@headers.last.body = str
|
229
|
+
end
|
230
|
+
|
231
|
+
# Get binary string
|
232
|
+
# @return [String]
|
233
|
+
def to_s
|
234
|
+
@headers.first.to_s
|
235
|
+
end
|
236
|
+
|
237
|
+
# Write a PCapNG file to disk.
|
238
|
+
# @param [String] filename
|
239
|
+
# @return [Array] see return from {PcapNG::File#to_file}
|
240
|
+
# @see File
|
241
|
+
def to_f(filename)
|
242
|
+
File.new.array_to_file(filename: filename, array: [self])
|
243
|
+
end
|
244
|
+
alias :write :to_f
|
245
|
+
|
246
|
+
# send packet on wire. Use first header +#to_w+ method.
|
247
|
+
# @param [String] iface interface name. Default to first non-loopback interface
|
248
|
+
# @return [void]
|
249
|
+
def to_w(iface=nil)
|
250
|
+
iface ||= PacketGen.default_iface
|
251
|
+
if @headers.first.respond_to? :to_w
|
252
|
+
@headers.first.to_w(iface)
|
253
|
+
else
|
254
|
+
type = @headers.first.class.to_s.gsub(/.*::/, '')
|
255
|
+
raise WireError, "don't known how to send a #{type} packet on wire"
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# @return [String]
|
260
|
+
def inspect
|
261
|
+
str = dashed_line(self.class)
|
262
|
+
@headers.each do |header|
|
263
|
+
str << dashed_line(header.class, 2)
|
264
|
+
header.to_h.each do |attr, value|
|
265
|
+
next if attr == :body
|
266
|
+
str << inspect_line(attr, value, 2)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
str << inspect_body
|
270
|
+
end
|
271
|
+
|
272
|
+
# @param [Packet] other
|
273
|
+
# @return [Boolean]
|
274
|
+
def ==(other)
|
275
|
+
to_s == other.to_s
|
276
|
+
end
|
277
|
+
|
278
|
+
private
|
279
|
+
|
280
|
+
# @overload header(protocol, layer=1)
|
281
|
+
# @param [String] protocol
|
282
|
+
# @param [Integer] layer
|
283
|
+
# @overload header(protocol, options)
|
284
|
+
# @param [String] protocol
|
285
|
+
# @param [Hash] options
|
286
|
+
# @return [Header::Base]
|
287
|
+
# @raise [ArgumentError] unknown protocol
|
288
|
+
def header(protocol, arg)
|
289
|
+
klass = check_protocol protocol
|
290
|
+
|
291
|
+
headers = @headers.select { |h| h.is_a? klass }
|
292
|
+
layer = arg.is_a?(Integer) ? arg : 1
|
293
|
+
header = headers[layer - 1]
|
294
|
+
|
295
|
+
if arg.is_a? Hash
|
296
|
+
arg.each do |key, value|
|
297
|
+
unless header.respond_to? "#{key}="
|
298
|
+
raise ArgumentError, "unknown #{key} attribute for #{header.class}"
|
299
|
+
end
|
300
|
+
header.send "#{key}=", value
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
header
|
305
|
+
end
|
306
|
+
|
307
|
+
# check if protocol is known
|
308
|
+
# @param [String] protocol
|
309
|
+
# @raise [ArgumentError] unknown protocol
|
310
|
+
def check_protocol(protocol)
|
311
|
+
unless Header.const_defined? protocol
|
312
|
+
raise ArgumentError, "unknown #{protocol} protocol"
|
313
|
+
end
|
314
|
+
klass = Header.const_get(protocol)
|
315
|
+
raise ArgumentError, "unknown #{protocol} protocol" unless klass.is_a? Class
|
316
|
+
klass
|
317
|
+
end
|
318
|
+
|
319
|
+
def dashed_line(name, level=1)
|
320
|
+
str = '--' * level << " #{name} "
|
321
|
+
str << '-' * (INSPECT_MAX_WIDTH - str.length) << "\n"
|
322
|
+
end
|
323
|
+
|
324
|
+
def inspect_line(attr, value, level=1)
|
325
|
+
str = ' ' + ' ' * level
|
326
|
+
val = if value.is_a? StructFu::Int
|
327
|
+
sz = value.to_s.size
|
328
|
+
"%-10s (0x%0#{2*sz}x)" % [value.to_i, value.to_i]
|
329
|
+
elsif value.respond_to? :to_x
|
330
|
+
value.to_x
|
331
|
+
else
|
332
|
+
value.to_s
|
333
|
+
end
|
334
|
+
str << "%7s %10s: %s" % [value.class.to_s.sub(/.*::/, ''), attr, val]
|
335
|
+
str << "\n"
|
336
|
+
end
|
337
|
+
|
338
|
+
def inspect_body
|
339
|
+
str = dashed_line('Body', 2)
|
340
|
+
str << (0..15).to_a.map { |v| " %02d" % v}.join << "\n"
|
341
|
+
str << '-' * INSPECT_MAX_WIDTH << "\n"
|
342
|
+
if body.size > 0
|
343
|
+
(body.size / 16 + 1).times do |i|
|
344
|
+
octets = body.to_s[i*16, 16].unpack('C*')
|
345
|
+
o_str = octets.map { |v| " %02x" % v}.join
|
346
|
+
str << o_str
|
347
|
+
str << ' ' * (3*16 - o_str.size) unless o_str.size >= 3*16
|
348
|
+
str << ' ' << octets.map { |v| v < 128 && v > 13 ? v.chr : '.' }.join
|
349
|
+
str << "\n"
|
350
|
+
end
|
351
|
+
end
|
352
|
+
str << '-' * INSPECT_MAX_WIDTH << "\n"
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
require_relative 'header'
|