DIY-pcap 0.2.5 → 0.2.6
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.
- data/bin/pcap +2 -62
- data/bin/rpcap +2 -63
- data/lib/diy/command.rb +80 -0
- data/lib/diy/device_finder.rb +1 -1
- data/lib/diy/dig.rb +3 -1
- data/lib/diy/live.rb +5 -0
- data/lib/diy/parser/mu/fixnum_ext.rb +7 -0
- data/lib/diy/parser/mu/pcap/ethernet.rb +148 -0
- data/lib/diy/parser/mu/pcap/header.rb +75 -0
- data/lib/diy/parser/mu/pcap/io_pair.rb +67 -0
- data/lib/diy/parser/mu/pcap/io_wrapper.rb +76 -0
- data/lib/diy/parser/mu/pcap/ip.rb +61 -0
- data/lib/diy/parser/mu/pcap/ipv4.rb +257 -0
- data/lib/diy/parser/mu/pcap/ipv6.rb +148 -0
- data/lib/diy/parser/mu/pcap/packet.rb +104 -0
- data/lib/diy/parser/mu/pcap/pkthdr.rb +155 -0
- data/lib/diy/parser/mu/pcap/reader.rb +61 -0
- data/lib/diy/parser/mu/pcap/reader/http_family.rb +170 -0
- data/lib/diy/parser/mu/pcap/sctp.rb +367 -0
- data/lib/diy/parser/mu/pcap/sctp/chunk.rb +123 -0
- data/lib/diy/parser/mu/pcap/sctp/chunk/data.rb +134 -0
- data/lib/diy/parser/mu/pcap/sctp/chunk/init.rb +100 -0
- data/lib/diy/parser/mu/pcap/sctp/chunk/init_ack.rb +68 -0
- data/lib/diy/parser/mu/pcap/sctp/parameter.rb +110 -0
- data/lib/diy/parser/mu/pcap/sctp/parameter/ip_address.rb +48 -0
- data/lib/diy/parser/mu/pcap/stream_packetizer.rb +72 -0
- data/lib/diy/parser/mu/pcap/tcp.rb +505 -0
- data/lib/diy/parser/mu/pcap/udp.rb +69 -0
- data/lib/diy/parser/mu/scenario/pcap.rb +172 -0
- data/lib/diy/parser/mu/scenario/pcap/fields.rb +50 -0
- data/lib/diy/parser/mu/scenario/pcap/rtp.rb +71 -0
- data/lib/diy/parser/pcap.rb +113 -0
- data/lib/diy/parser/readme.md +72 -0
- data/lib/diy/utils.rb +9 -1
- data/lib/diy/version.rb +1 -1
- data/lib/diy/worker.rb +3 -2
- data/lib/diy/worker_keeper.rb +6 -0
- data/spec/helper/tcp.dat +0 -0
- data/spec/live_spec.rb +9 -0
- data/spec/mu_parser_spec.rb +12 -0
- data/spec/utils_spec.rb +1 -1
- metadata +34 -3
data/bin/pcap
CHANGED
@@ -1,67 +1,7 @@
|
|
1
1
|
$LOAD_PATH.unshift File.join( File.dirname(__FILE__), '..', 'lib' )
|
2
2
|
require 'rubygems'
|
3
3
|
require 'diy-pcap'
|
4
|
-
require 'diy/task'
|
5
4
|
|
6
|
-
|
5
|
+
$_PORT = "7878"
|
7
6
|
|
8
|
-
|
9
|
-
:ip => "0.0.0.0:7878",
|
10
|
-
}
|
11
|
-
|
12
|
-
OptionParser.new do |opts|
|
13
|
-
|
14
|
-
opts.on("-f file", "File will be parsed") do |v|
|
15
|
-
options[:file] = v
|
16
|
-
end
|
17
|
-
|
18
|
-
opts.on("-i ip", "--ip ip", "client or server : 0.0.0.0 or 0.0.0.0:7878", "default is 0.0.0.0:7878") do |v|
|
19
|
-
options[:ip] = v
|
20
|
-
end
|
21
|
-
|
22
|
-
opts.on("-n device_name", "--name device_name", "Send or Recv device name") do |v|
|
23
|
-
options[:device_name] = v
|
24
|
-
end
|
25
|
-
|
26
|
-
opts.on_tail("--show", "Show all devices name and exit") do
|
27
|
-
require 'diy/device_finder'
|
28
|
-
DIY::DeviceFinder.pp_devices
|
29
|
-
exit 0
|
30
|
-
end
|
31
|
-
|
32
|
-
opts.on_tail('-v','--version', 'Show version') do
|
33
|
-
puts DIY::PCAP::VERSION
|
34
|
-
exit 0
|
35
|
-
end
|
36
|
-
|
37
|
-
opts.on_tail('-V', 'detail mode') do
|
38
|
-
DIY::Logger.level = ::Logger::DEBUG
|
39
|
-
end
|
40
|
-
|
41
|
-
opts.on_tail('-h','--help', 'Show this help') do
|
42
|
-
puts opts
|
43
|
-
exit 0
|
44
|
-
end
|
45
|
-
end.parse!
|
46
|
-
|
47
|
-
if options[:file]
|
48
|
-
require File.join( Dir.pwd, options[:file] )
|
49
|
-
else
|
50
|
-
ip = options[:ip]
|
51
|
-
if ip.include?(':')
|
52
|
-
uri = "druby://#{ip}"
|
53
|
-
else
|
54
|
-
uri = "druby://#{ip}:7878"
|
55
|
-
end
|
56
|
-
|
57
|
-
if options[:device_name]
|
58
|
-
device_name = options[:device_name]
|
59
|
-
else
|
60
|
-
require 'diy/device_finder'
|
61
|
-
device_name = DIY::DeviceFinder.smart_select
|
62
|
-
end
|
63
|
-
DIY::Logger.info( "Initialize Live: #{device_name}" )
|
64
|
-
device = DIY::Live.new(device_name) #FFI::PCap::Live.new(:dev=>device_name, :handler => FFI::PCap::Handler, :promisc => true)
|
65
|
-
worker = DIY::Worker.new(device)
|
66
|
-
DIY::WorkerKeeper.new(worker, uri).run
|
67
|
-
end
|
7
|
+
require 'diy/command'
|
data/bin/rpcap
CHANGED
@@ -1,68 +1,7 @@
|
|
1
1
|
$LOAD_PATH.unshift File.join( File.dirname(__FILE__), '..', 'lib' )
|
2
2
|
require 'rubygems'
|
3
3
|
require 'diy-pcap'
|
4
|
-
require 'diy/task'
|
5
4
|
|
6
|
-
|
5
|
+
$_PORT = "7879"
|
7
6
|
|
8
|
-
|
9
|
-
:ip => "0.0.0.0:7879",
|
10
|
-
}
|
11
|
-
|
12
|
-
OptionParser.new do |opts|
|
13
|
-
|
14
|
-
opts.on("-f file", "File will be parsed") do |v|
|
15
|
-
options[:file] = v
|
16
|
-
end
|
17
|
-
|
18
|
-
opts.on("-i ip", "--ip ip", "client or server : 0.0.0.0 or 0.0.0.0:7879", "default is 0.0.0.0:7879") do |v|
|
19
|
-
options[:ip] = v
|
20
|
-
end
|
21
|
-
|
22
|
-
opts.on("-n device_name", "--name device_name", "Send or Recv device name") do |v|
|
23
|
-
options[:device_name] = v
|
24
|
-
end
|
25
|
-
|
26
|
-
opts.on_tail("--show", "Show all devices name and exit") do
|
27
|
-
require 'diy/device_finder'
|
28
|
-
DIY::DeviceFinder.pp_devices
|
29
|
-
exit 0
|
30
|
-
end
|
31
|
-
|
32
|
-
opts.on_tail('-v','--version', 'Show version') do
|
33
|
-
puts DIY::PCAP::VERSION
|
34
|
-
exit 0
|
35
|
-
end
|
36
|
-
|
37
|
-
opts.on_tail('-V', 'detail mode') do
|
38
|
-
DIY::Logger.level = ::Logger::DEBUG
|
39
|
-
end
|
40
|
-
|
41
|
-
opts.on_tail('-h','--help', 'Show this help') do
|
42
|
-
puts opts
|
43
|
-
exit 0
|
44
|
-
end
|
45
|
-
end.parse!
|
46
|
-
|
47
|
-
if options[:file]
|
48
|
-
$SERVER = true
|
49
|
-
require File.join( Dir.pwd, options[:file])
|
50
|
-
else
|
51
|
-
ip = options[:ip]
|
52
|
-
if ip.include?(':')
|
53
|
-
uri = "druby://#{ip}"
|
54
|
-
else
|
55
|
-
uri = "druby://#{ip}:7879"
|
56
|
-
end
|
57
|
-
|
58
|
-
if options[:device_name]
|
59
|
-
device_name = options[:device_name]
|
60
|
-
else
|
61
|
-
require 'diy/device_finder'
|
62
|
-
device_name = DIY::DeviceFinder.smart_select
|
63
|
-
end
|
64
|
-
DIY::Logger.info( "Initialize Live: #{device_name}" )
|
65
|
-
device = DIY::Live.new(device_name) #FFI::PCap::Live.new(:dev=>device_name, :handler => FFI::PCap::Handler, :promisc => true)
|
66
|
-
worker = DIY::Worker.new(device)
|
67
|
-
DIY::WorkerKeeper.new(worker, uri).run
|
68
|
-
end
|
7
|
+
require 'diy/command'
|
data/lib/diy/command.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
|
2
|
+
require 'diy/task'
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
pcap_port = "7878"
|
6
|
+
rpcap_port = "7879"
|
7
|
+
|
8
|
+
if ! $_PORT
|
9
|
+
puts "Must set $_PORT value before call me"
|
10
|
+
exit 1
|
11
|
+
end
|
12
|
+
|
13
|
+
options = {
|
14
|
+
:ip => "0.0.0.0:#{$_PORT}",
|
15
|
+
}
|
16
|
+
|
17
|
+
OptionParser.new do |opts|
|
18
|
+
|
19
|
+
opts.on("-f file", "File will be parsed") do |v|
|
20
|
+
options[:file] = v
|
21
|
+
end
|
22
|
+
|
23
|
+
opts.on("-i ip", "--ip ip", "client or server : 0.0.0.0 or 0.0.0.0:#{$_PORT}", "default is 0.0.0.0:#{$_PORT}") do |v|
|
24
|
+
options[:ip] = v
|
25
|
+
end
|
26
|
+
|
27
|
+
opts.on("-n device_name", "--name device_name", "Send or Recv device name") do |v|
|
28
|
+
options[:device_name] = v
|
29
|
+
end
|
30
|
+
|
31
|
+
opts.on_tail("--show", "Show all devices name and exit") do
|
32
|
+
require 'diy/device_finder'
|
33
|
+
DIY::DeviceFinder.pp_devices
|
34
|
+
exit 0
|
35
|
+
end
|
36
|
+
|
37
|
+
opts.on_tail("--timer","Use TimerIdConv module instead of DRb's default idconv") do
|
38
|
+
options[:timer] = true
|
39
|
+
end
|
40
|
+
|
41
|
+
opts.on_tail('-v','--version', 'Show version') do
|
42
|
+
puts DIY::PCAP::VERSION
|
43
|
+
exit 0
|
44
|
+
end
|
45
|
+
|
46
|
+
opts.on_tail('-V', 'detail mode') do
|
47
|
+
DIY::Logger.level = ::Logger::DEBUG
|
48
|
+
DIY::Logger.debug "Enable debug mode"
|
49
|
+
end
|
50
|
+
|
51
|
+
opts.on_tail('-h','--help', 'Show this help and exit') do
|
52
|
+
puts opts
|
53
|
+
exit 0
|
54
|
+
end
|
55
|
+
end.parse!
|
56
|
+
|
57
|
+
if options[:file]
|
58
|
+
require File.join( Dir.pwd, options[:file] )
|
59
|
+
else
|
60
|
+
ip = options[:ip]
|
61
|
+
if ip.include?(':')
|
62
|
+
uri = "druby://#{ip}"
|
63
|
+
else
|
64
|
+
uri = "druby://#{ip}:#{$_PORT}"
|
65
|
+
end
|
66
|
+
|
67
|
+
if options[:device_name]
|
68
|
+
device_name = options[:device_name]
|
69
|
+
else
|
70
|
+
require 'diy/device_finder'
|
71
|
+
device_name = DIY::DeviceFinder.smart_select
|
72
|
+
end
|
73
|
+
device = DIY::Live.new(device_name)
|
74
|
+
worker = DIY::Worker.new(device)
|
75
|
+
worker_keeper = DIY::WorkerKeeper.new(worker, uri)
|
76
|
+
if options[:timer]
|
77
|
+
worker_keeper.use_timeridconv
|
78
|
+
end
|
79
|
+
worker_keeper.run
|
80
|
+
end
|
data/lib/diy/device_finder.rb
CHANGED
data/lib/diy/dig.rb
CHANGED
data/lib/diy/live.rb
CHANGED
@@ -7,6 +7,7 @@ module DIY
|
|
7
7
|
def initialize(device_name)
|
8
8
|
DIY::Logger.info( "Initialize Live: #{device_name}" )
|
9
9
|
@live = FFI::PCap::Live.new(:dev=>device_name, :handler => FFI::PCap::CopyHandler, :promisc => true)
|
10
|
+
DIY::Logger.info( "Listen on: #{net} " )
|
10
11
|
#~ @live.non_blocking= true
|
11
12
|
end
|
12
13
|
attr_reader :live
|
@@ -27,6 +28,10 @@ module DIY
|
|
27
28
|
end
|
28
29
|
end
|
29
30
|
|
31
|
+
def net
|
32
|
+
@live.network + " / " + @live.netmask
|
33
|
+
end
|
34
|
+
|
30
35
|
end # end of class Live
|
31
36
|
|
32
37
|
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
# http://www.mudynamics.com
|
2
|
+
# http://labs.mudynamics.com
|
3
|
+
# http://www.pcapr.net
|
4
|
+
|
5
|
+
module Mu
|
6
|
+
class Pcap
|
7
|
+
|
8
|
+
class Ethernet < Packet
|
9
|
+
attr_accessor :src, :dst, :type
|
10
|
+
|
11
|
+
ETHERTYPE_IP = 0x0800
|
12
|
+
ETHERTYPE_IP6 = 0x86dd
|
13
|
+
ETHERTYPE_ARP = 0x0806
|
14
|
+
ETHERTYPE_PPPOE_SESSION = 0x8864
|
15
|
+
ETHERTYPE_802_1Q = 0X8100
|
16
|
+
|
17
|
+
PPP_IP = 0x0021
|
18
|
+
PPP_IPV6 = 0x0057
|
19
|
+
|
20
|
+
def initialize src=nil, dst=nil, type=0
|
21
|
+
super()
|
22
|
+
@src = src
|
23
|
+
@dst = dst
|
24
|
+
@type = type
|
25
|
+
end
|
26
|
+
|
27
|
+
def flow_id
|
28
|
+
if not @payload or @payload.is_a? String
|
29
|
+
return [:ethernet, @src, @dst, @type]
|
30
|
+
else
|
31
|
+
return @payload.flow_id
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
FMT_MAC = "C6"
|
36
|
+
FMT_n = 'n'
|
37
|
+
MAC_TEMPLATE = '%02x:%02x:%02x:%02x:%02x:%02x'
|
38
|
+
def self.from_bytes bytes
|
39
|
+
if bytes.length < 14
|
40
|
+
raise ParseError, "Truncated Ethernet header: expected 14 bytes, got #{bytes.length} bytes"
|
41
|
+
end
|
42
|
+
|
43
|
+
dst = bytes.slice!(0,6).unpack FMT_MAC
|
44
|
+
dst = MAC_TEMPLATE % dst
|
45
|
+
src = bytes.slice!(0,6).unpack FMT_MAC
|
46
|
+
src = MAC_TEMPLATE % src
|
47
|
+
type = bytes.slice!(0,2).unpack(FMT_n)[0]
|
48
|
+
while (type == ETHERTYPE_802_1Q)
|
49
|
+
# Skip 4 bytes for 802.1q vlan tag field
|
50
|
+
bytes.slice!(0,2)
|
51
|
+
type = bytes.slice!(0,2).unpack(FMT_n)[0]
|
52
|
+
end
|
53
|
+
ethernet = Ethernet.new src, dst, type
|
54
|
+
ethernet.payload = bytes
|
55
|
+
ethernet.payload_raw = bytes
|
56
|
+
begin
|
57
|
+
case type
|
58
|
+
when ETHERTYPE_IP
|
59
|
+
ethernet.payload = IPv4.from_bytes bytes
|
60
|
+
when ETHERTYPE_IP6
|
61
|
+
ethernet.payload = IPv6.from_bytes bytes
|
62
|
+
when ETHERTYPE_PPPOE_SESSION
|
63
|
+
# Remove PPPoE/PPP session layer
|
64
|
+
ethernet.payload = bytes
|
65
|
+
ethernet.remove_pppoe!
|
66
|
+
else
|
67
|
+
ethernet.payload = bytes
|
68
|
+
end
|
69
|
+
rescue ParseError => e
|
70
|
+
Pcap.warning e
|
71
|
+
end
|
72
|
+
return ethernet
|
73
|
+
end
|
74
|
+
|
75
|
+
def ip?
|
76
|
+
return payload.is_a?(IP)
|
77
|
+
end
|
78
|
+
|
79
|
+
ADDR_TO_BYTES = {}
|
80
|
+
FMT_HEADER = 'a6a6n'
|
81
|
+
def write io
|
82
|
+
dst_mac = ADDR_TO_BYTES[@dst] ||= @dst.split(':').inject('') {|m, b| m << b.to_i(16).chr}
|
83
|
+
src_mac = ADDR_TO_BYTES[@src] ||= @src.split(':').inject('') {|m, b| m << b.to_i(16).chr}
|
84
|
+
bytes = [dst_mac, src_mac, @type].pack(FMT_HEADER)
|
85
|
+
io.write bytes
|
86
|
+
if @payload.is_a? String
|
87
|
+
io.write @payload
|
88
|
+
else
|
89
|
+
@payload.write io
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Remove the PPPoE and PPP headers. PPPoE is documented in RFC 2516.
|
94
|
+
def remove_pppoe!
|
95
|
+
bytes = self.payload_raw
|
96
|
+
|
97
|
+
# Remove PPPoE header
|
98
|
+
Pcap.assert bytes.length >= 6, 'Truncated PPPoE header: ' +
|
99
|
+
"expected at least 6 bytes, got #{bytes.length} bytes"
|
100
|
+
version_type, code, session_id, length = bytes.unpack 'CCnn'
|
101
|
+
version = version_type >> 4 & 0b1111
|
102
|
+
type = version_type & 0b1111
|
103
|
+
Pcap.assert version == 1, "Unknown PPPoE version: #{version}"
|
104
|
+
Pcap.assert type == 1, "Unknown PPPoE type: #{type}"
|
105
|
+
Pcap.assert code == 0, "Unknown PPPoE code: #{code}"
|
106
|
+
bytes = bytes[6..-1]
|
107
|
+
Pcap.assert bytes.length >= length, 'Truncated PPoE packet: ' +
|
108
|
+
"expected #{length} bytes, got #{bytes.length} bytes"
|
109
|
+
|
110
|
+
# Remove PPP header
|
111
|
+
Pcap.assert bytes.length >= 2, 'Truncated PPP packet: ' +
|
112
|
+
"expected at least bytes, got #{bytes.length} bytes"
|
113
|
+
protocol_id, = bytes.unpack 'n'
|
114
|
+
bytes = bytes[2..-1]
|
115
|
+
case protocol_id
|
116
|
+
when PPP_IP
|
117
|
+
self.payload = IPv4.from_bytes bytes
|
118
|
+
self.payload_raw = bytes
|
119
|
+
self.type = ETHERTYPE_IP
|
120
|
+
when PPP_IPV6
|
121
|
+
self.payload = IPv6.from_bytes bytes
|
122
|
+
self.payload_raw = bytes
|
123
|
+
self.type = ETHERTYPE_IP6
|
124
|
+
else
|
125
|
+
# Failed. Don't update payload or type.
|
126
|
+
raise ParseError, "Unknown PPP protocol: 0x#{'%04x' % protocol_id}"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def to_s
|
131
|
+
if @payload.is_a? String
|
132
|
+
payload = @payload.inspect
|
133
|
+
else
|
134
|
+
payload = @payload.to_s
|
135
|
+
end
|
136
|
+
return "ethernet(%s, %s, %d, %s)" % [@src, @dst, @type, payload]
|
137
|
+
end
|
138
|
+
|
139
|
+
def == other
|
140
|
+
return super &&
|
141
|
+
self.src == other.src &&
|
142
|
+
self.dst == other.dst &&
|
143
|
+
self.type == other.type
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# http://www.mudynamics.com
|
2
|
+
# http://labs.mudynamics.com
|
3
|
+
# http://www.pcapr.net
|
4
|
+
|
5
|
+
module Mu
|
6
|
+
class Pcap
|
7
|
+
|
8
|
+
class Header
|
9
|
+
attr_accessor :magic, :version_major, :version_minor, :thiszone, :sigfigs,
|
10
|
+
:snaplen, :linktype
|
11
|
+
|
12
|
+
BIG_ENDIAN_FORMAT = 'nnNNNN'
|
13
|
+
LITTLE_ENDIAN_FORMAT = 'vvVVVV'
|
14
|
+
|
15
|
+
UNSUPPORTED_FORMATS = {
|
16
|
+
0x474D4255 => "NetMon", # "GMBU"
|
17
|
+
0x5452534E => "NA Sniffer (DOS)" # Starts with "TRSNIFF data"
|
18
|
+
}
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
@magic = BIG_ENDIAN
|
22
|
+
@version_major = 2
|
23
|
+
@version_minor = 4
|
24
|
+
@thiszone = 0
|
25
|
+
@sigfigs = 0
|
26
|
+
@snaplen = 1500
|
27
|
+
@linktype = DLT_NULL
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.read ios
|
31
|
+
header = Header.new
|
32
|
+
bytes = ios.read 24
|
33
|
+
Pcap.assert bytes, 'PCAP header missing'
|
34
|
+
Pcap.assert bytes.length == 24, 'Truncated PCAP header: ' +
|
35
|
+
"expected 24 bytes, got #{bytes.length} bytes"
|
36
|
+
header.magic, _ = bytes[0, 4].unpack 'N'
|
37
|
+
if header.magic == BIG_ENDIAN
|
38
|
+
format = BIG_ENDIAN_FORMAT
|
39
|
+
elsif header.magic == LITTLE_ENDIAN
|
40
|
+
format = LITTLE_ENDIAN_FORMAT
|
41
|
+
else
|
42
|
+
format = UNSUPPORTED_FORMATS[header.magic]
|
43
|
+
if format.nil?
|
44
|
+
err = "Unsupported packet capture format. "
|
45
|
+
else
|
46
|
+
err = "#{format} capture files are not supported. "
|
47
|
+
end
|
48
|
+
raise ParseError, err
|
49
|
+
end
|
50
|
+
header.version_major, header.version_minor, header.thiszone,
|
51
|
+
header.sigfigs, header.snaplen, header.linktype =
|
52
|
+
bytes[4..-1].unpack format
|
53
|
+
return header
|
54
|
+
end
|
55
|
+
|
56
|
+
def write io
|
57
|
+
bytes = [BIG_ENDIAN, @version_major, @version_minor, @thiszone,
|
58
|
+
@sigfigs, @snaplen, DLT_EN10MB].pack('N' + BIG_ENDIAN_FORMAT)
|
59
|
+
io.write bytes
|
60
|
+
end
|
61
|
+
|
62
|
+
def == other
|
63
|
+
return self.class == other.class &&
|
64
|
+
self.magic == other.magic &&
|
65
|
+
self.version_major == other.version_major &&
|
66
|
+
self.version_minor == other.version_minor &&
|
67
|
+
self.thiszone == other.thiszone &&
|
68
|
+
self.sigfigs == other.sigfigs &&
|
69
|
+
self.snaplen == other.snaplen &&
|
70
|
+
self.linktype == other.linktype
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|