emtraceroute 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +20 -0
- data/README.md +57 -0
- data/Rakefile +1 -0
- data/bin/traceroute +61 -0
- data/emtraceroute.gemspec +19 -0
- data/lib/emtraceroute.rb +28 -0
- data/lib/emtraceroute/ext/socket.rb +14 -0
- data/lib/emtraceroute/handler.rb +123 -0
- data/lib/emtraceroute/hop.rb +42 -0
- data/lib/emtraceroute/icmp.rb +41 -0
- data/lib/emtraceroute/ip.rb +47 -0
- data/lib/emtraceroute/version.rb +5 -0
- metadata +75 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 kubo39
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
---
|
2
|
+
emtraceroute
|
3
|
+
---
|
4
|
+
|
5
|
+
- This traceroute is based on eventmachine.
|
6
|
+
|
7
|
+
## This is how it works
|
8
|
+
|
9
|
+
$ sudo traceroute 'google.co.jp'
|
10
|
+
1. 0.004s :: 192.168.X.XXX :: Reserved
|
11
|
+
2. 0.008s :: 118.23.XX.XXX :: Japan
|
12
|
+
3. 0.007s :: 118.23.XX.XXX :: Japan
|
13
|
+
4. 0.009s :: 221.184.X.XXX :: Japan, Niigata, Niigata
|
14
|
+
5. 0.008s :: 60.37.XX.XX :: Japan
|
15
|
+
6. 0.009s :: 60.37.XX.XXX :: Japan
|
16
|
+
7. 0.008s :: 60.37.XX.XXX :: Japan
|
17
|
+
8. 0.01s :: 118.23.XX.XXX :: Japan
|
18
|
+
9. 0.04s :: 211.129.XX.XX :: Japan
|
19
|
+
10. 0.01s :: 209.85.XXX.XX :: United States, California, Mountain View
|
20
|
+
11. 0.01s :: 209.85.XXX.XXX :: United States, California, Mountain View
|
21
|
+
12. 0.011s :: 173.194.XX.XX :: United States, California, Mountain View
|
22
|
+
|
23
|
+
## How to install
|
24
|
+
|
25
|
+
It's easy!
|
26
|
+
|
27
|
+
sudo gem install emtraceroute
|
28
|
+
|
29
|
+
#### or
|
30
|
+
|
31
|
+
first, clone from github
|
32
|
+
|
33
|
+
git clone https://github.com/kubo39/emtraceroute.git
|
34
|
+
|
35
|
+
second, build gemspec
|
36
|
+
|
37
|
+
gem build emtraceroute.gemspec
|
38
|
+
|
39
|
+
third, gem install
|
40
|
+
|
41
|
+
sudo gem install emtraceroute --local
|
42
|
+
|
43
|
+
## options
|
44
|
+
|
45
|
+
-t, --timeout: hop timeout seconds.
|
46
|
+
|
47
|
+
-r, --tries: retry counts.
|
48
|
+
|
49
|
+
-m, --max_hops: max size of traceroute hops.
|
50
|
+
|
51
|
+
-s, --silent: only show results at the end.
|
52
|
+
|
53
|
+
-g, --no-geoip: not display geoip location.
|
54
|
+
|
55
|
+
## LICENSE
|
56
|
+
|
57
|
+
see LICENSE.txt
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/traceroute
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'emtraceroute'
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
if Process.uid != 0
|
6
|
+
puts "traceroute needs root privileges for the raw socket"
|
7
|
+
exit(1)
|
8
|
+
end
|
9
|
+
|
10
|
+
defaults = {
|
11
|
+
"hop_callback" => lambda {|hop| puts hop },
|
12
|
+
"timeout" => 2,
|
13
|
+
"max_tries" => 3,
|
14
|
+
"max_hops" => 30,
|
15
|
+
"geoip_lookup" => true
|
16
|
+
}
|
17
|
+
|
18
|
+
if ARGV.size < 1
|
19
|
+
puts "Usage: #{$0} [options]"
|
20
|
+
puts "#{$0}: Try --help for usage details."
|
21
|
+
exit
|
22
|
+
end
|
23
|
+
|
24
|
+
opts = {}
|
25
|
+
|
26
|
+
config = OptionParser.new
|
27
|
+
|
28
|
+
config.on("-t [VAL]", "--timeout [VAL]") {|v| opts["timeout"] = v.to_i }
|
29
|
+
config.on("-r [VAL]", "--tries [VAL]") {|v| opts["tries"] = v.to_i }
|
30
|
+
config.on("-m [VAL]", "--max_hops [VAL]") {|v| opts["max_hops"] = v.to_i }
|
31
|
+
config.on("-s", "--silent") { opts["silent"] = nil }
|
32
|
+
config.on("-g", "--no-geoip") { opts["geoip_lookup"] = false }
|
33
|
+
|
34
|
+
config.parse!(ARGV)
|
35
|
+
target = ARGV.last[0] != "-" ? ARGV.pop : nil
|
36
|
+
|
37
|
+
begin
|
38
|
+
unless target
|
39
|
+
raise OptionParser::InvalidArgument
|
40
|
+
end
|
41
|
+
rescue => ex
|
42
|
+
puts ex.message
|
43
|
+
exit(1)
|
44
|
+
end
|
45
|
+
|
46
|
+
settings = defaults.dup
|
47
|
+
|
48
|
+
settings["timeout"] = opts["timeout"] if opts.include? "timeout"
|
49
|
+
settings["max_tries"] = opts["tries"] if opts.include? "tries"
|
50
|
+
settings["max_hops"] = opts["max_hops"] if opts.include? "max_hops"
|
51
|
+
settings["hop_callback"] = opts["silent"] if opts.include? "silent"
|
52
|
+
settings["geoip_lookup"] = opts["geoip_lookup"] if opts.include? "geoip_lookup"
|
53
|
+
|
54
|
+
begin
|
55
|
+
target = IPSocket.getaddress(target)
|
56
|
+
rescue Exception => ex
|
57
|
+
puts "Couldn't resolve #{target}: #{ex}"
|
58
|
+
exit(1)
|
59
|
+
end
|
60
|
+
|
61
|
+
EM.run { EM::Traceroute.start_trace(target, settings) }
|
@@ -0,0 +1,19 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require './lib/emtraceroute/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "emtraceroute"
|
7
|
+
s.version = EM::Traceroute::VERSION
|
8
|
+
s.authors = ["kubo39"]
|
9
|
+
s.email = "kubo39@gmail.com"
|
10
|
+
s.description = %q{traceroute utility on EventMachine}
|
11
|
+
s.summary = s.description
|
12
|
+
s.homepage = "https://github.com/kubo39/emtraceroute"
|
13
|
+
|
14
|
+
s.add_runtime_dependency('eventmachine', '~> 1.0.0.beta.3')
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split($/)
|
17
|
+
s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
end
|
data/lib/emtraceroute.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'socket'
|
3
|
+
require 'eventmachine'
|
4
|
+
require 'open-uri'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
require_relative 'emtraceroute/version'
|
8
|
+
require_relative 'emtraceroute/ext/socket'
|
9
|
+
require_relative 'emtraceroute/ip'
|
10
|
+
require_relative 'emtraceroute/icmp'
|
11
|
+
require_relative 'emtraceroute/hop'
|
12
|
+
require_relative 'emtraceroute/handler'
|
13
|
+
|
14
|
+
|
15
|
+
module EM::Traceroute
|
16
|
+
def self.start_trace target, settings
|
17
|
+
fd = Socket.new(Socket::AF_INET, Socket::SOCK_RAW, Socket::IPPROTO_ICMP)
|
18
|
+
fd.setsockopt(Socket::IPPROTO_IP, Socket::IP_HDRINCL, 1)
|
19
|
+
|
20
|
+
conn = EM.watch(fd, EM::TracerouteHandler, target, settings)
|
21
|
+
conn.notify_readable = true
|
22
|
+
conn.notify_writable = true
|
23
|
+
|
24
|
+
unless settings["hop_callback"]
|
25
|
+
conn.deferred.callback {|hop| puts hop }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module EM::TracerouteHandler
|
2
|
+
attr_reader :deferred
|
3
|
+
def initialize target, settings
|
4
|
+
@target = target
|
5
|
+
@settings = settings
|
6
|
+
end
|
7
|
+
|
8
|
+
def post_init
|
9
|
+
@hops = []
|
10
|
+
@out_queue = []
|
11
|
+
@waiting = true
|
12
|
+
|
13
|
+
@deferred = EM::DefaultDeferrable.new
|
14
|
+
|
15
|
+
# send first probe packet
|
16
|
+
@out_queue << EM::Traceroute::Hop.new(@target, 1)
|
17
|
+
end
|
18
|
+
|
19
|
+
def notify_readable
|
20
|
+
return if !@waiting || @hops.empty?
|
21
|
+
|
22
|
+
pkt = @io.recv(4096)
|
23
|
+
|
24
|
+
# disassemble ip header
|
25
|
+
ip = Iphdr.disassemble(pkt[0..19])
|
26
|
+
unless ip.proto != Socket::IPPROTO_ICMP
|
27
|
+
found = false
|
28
|
+
|
29
|
+
# disassemble icmp header
|
30
|
+
icmp = Icmphdr.disassemble(pkt[20..27])
|
31
|
+
if icmp.type == 0 && icmp.id == @hops.last.icmp.id
|
32
|
+
found = true
|
33
|
+
elsif icmp.type == 11
|
34
|
+
# disassemble referenced ip header
|
35
|
+
ref = Iphdr.disassemble(pkt[28..47])
|
36
|
+
found = true if ref.dst == @target
|
37
|
+
end
|
38
|
+
|
39
|
+
@waiting = false if ip.src == @target
|
40
|
+
|
41
|
+
hop_found(@hops.last, ip, icmp) if found
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def notify_writable
|
46
|
+
if @waiting && !(@out_queue.empty?)
|
47
|
+
hop = @out_queue.shift
|
48
|
+
pkt = hop.pkt
|
49
|
+
if @hops.empty? || !(@hops.empty?) && hop.ttl != @hops.last.ttl
|
50
|
+
@hops << hop
|
51
|
+
end
|
52
|
+
sockaddr = Socket.sockaddr_in(0, hop.ip.dst)
|
53
|
+
@io.send(pkt, 0, sockaddr)
|
54
|
+
|
55
|
+
timeout = @settings.fetch('timeout')
|
56
|
+
EM.add_timer(timeout) { hop_timeout }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def unbind
|
61
|
+
end
|
62
|
+
|
63
|
+
def hop_found(hop, ip, icmp)
|
64
|
+
hop.remote_ip = ip
|
65
|
+
hop.remote_icmp = icmp
|
66
|
+
|
67
|
+
if ip && icmp
|
68
|
+
hop.found = Time.now.to_f
|
69
|
+
|
70
|
+
if @settings.fetch "geoip_lookup"
|
71
|
+
|
72
|
+
url = "http://freegeoip.net/json/#{ip.src}"
|
73
|
+
|
74
|
+
page = open(url).read
|
75
|
+
d = JSON.load page
|
76
|
+
|
77
|
+
hop.location = [d["country_name"], d["region_name"], d["city"]].select do |s|
|
78
|
+
s && !(s.empty?)
|
79
|
+
end.join(", ").encode("utf-8")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
ttl = hop.ttl + 1
|
84
|
+
tail = @hops.last(2)
|
85
|
+
if tail.size == 2 && tail.first.remote_ip == ip ||
|
86
|
+
(ttl > (@settings.fetch("max_hops", 30) + 1))
|
87
|
+
done = true
|
88
|
+
else
|
89
|
+
done = false
|
90
|
+
end
|
91
|
+
|
92
|
+
unless done
|
93
|
+
if(cb = @settings.fetch "hop_callback")
|
94
|
+
cb.call(hop)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
unless @waiting
|
99
|
+
if @deferred
|
100
|
+
@deferred.set_deferred_status :succeeded, @hops
|
101
|
+
@deferred = nil
|
102
|
+
end
|
103
|
+
EM.stop
|
104
|
+
else
|
105
|
+
@out_queue << EM::Traceroute::Hop.new(@target, ttl)
|
106
|
+
end
|
107
|
+
rescue
|
108
|
+
detach
|
109
|
+
end
|
110
|
+
|
111
|
+
def hop_timeout *ign
|
112
|
+
hop = @hops.last
|
113
|
+
unless hop.found
|
114
|
+
if hop.tries < @settings.fetch("max_tries")
|
115
|
+
# retry
|
116
|
+
@out_queue << hop
|
117
|
+
else
|
118
|
+
# give up and move forward
|
119
|
+
hop_found(hop, nil, nil)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module EM::Traceroute
|
2
|
+
class Hop
|
3
|
+
attr_accessor :found, :tries, :remote_ip, :remote_icmp, :location, :ttl, :ip, :pkt, :icmp
|
4
|
+
def initialize target, ttl
|
5
|
+
@found = false
|
6
|
+
@tries = 0
|
7
|
+
@last_try = 0
|
8
|
+
@remote_ip = nil
|
9
|
+
@remote_icmp = nil
|
10
|
+
@location = ""
|
11
|
+
@ttl = ttl
|
12
|
+
@ip = Iphdr.new(Socket::IPPROTO_ICMP, '0.0.0.0', target) # IP header
|
13
|
+
@ip.ttl = ttl
|
14
|
+
@ip.id += ttl
|
15
|
+
|
16
|
+
@icmp = Icmphdr.new("traceroute") # ICMP header
|
17
|
+
@icmp.id = @ip.id
|
18
|
+
@ip.data = @icmp.assemble
|
19
|
+
|
20
|
+
@pkt = @ip.assemble
|
21
|
+
end
|
22
|
+
|
23
|
+
def pkt
|
24
|
+
@tries += 1
|
25
|
+
@last_try = Time.now.to_f
|
26
|
+
@pkt
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
if @found
|
31
|
+
ip = ":: #{remote_ip.src}"
|
32
|
+
ping = "#{(@found - @last_try).round(3)}s"
|
33
|
+
else
|
34
|
+
ip = "??"
|
35
|
+
ping = "-"
|
36
|
+
end
|
37
|
+
|
38
|
+
location = ":: #{@location}"
|
39
|
+
"#{@ttl}. #{ping} #{ip} #{location}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class Icmphdr
|
2
|
+
# This represents an ICMP packet header.
|
3
|
+
#
|
4
|
+
# Icmphdr#assemble packages the packet
|
5
|
+
# Imphdr.checsum calc checksum
|
6
|
+
# Icmphdr.disassemble disassembles the packet
|
7
|
+
#
|
8
|
+
attr_accessor :type, :code, :cksum, :id, :sequence, :data
|
9
|
+
def initialize data=""
|
10
|
+
@type = 8
|
11
|
+
@code = 0
|
12
|
+
@cksum = 0
|
13
|
+
@id = $$
|
14
|
+
@sequence = 0
|
15
|
+
@data = data
|
16
|
+
end
|
17
|
+
|
18
|
+
def assemble
|
19
|
+
part1 = [@type, @code].pack('C*')
|
20
|
+
part2 = [@id, @sequence].pack('n*')
|
21
|
+
cksum = Icmphdr.checksum(part1 + "\000\000" + part2 + @data)
|
22
|
+
cksum = [cksum].pack('n')
|
23
|
+
part1 + cksum + part2 + @data
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.checksum data
|
27
|
+
data += '\0' if data.size & 1 == 1
|
28
|
+
cksum = data.unpack("n*")[0..(data.size >> 1)].inject(&:+)
|
29
|
+
cksum = (cksum >> 16) + (cksum & 0xffff)
|
30
|
+
cksum += (cksum >> 16)
|
31
|
+
cksum = (cksum & 0xffff) ^ 0xffff
|
32
|
+
cksum
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.disassemble data
|
36
|
+
icmp = Icmphdr.new
|
37
|
+
pkt = data.unpack('CCnnn')
|
38
|
+
icmp.type, icmp.code, icmp.cksum, icmp.id, icmp.sequence = pkt
|
39
|
+
icmp
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class Iphdr
|
2
|
+
# This represents an IP packet header.
|
3
|
+
#
|
4
|
+
# Iphdr#assemble packages the packet
|
5
|
+
# Iphdr.disassemble disassembles the packet
|
6
|
+
#
|
7
|
+
attr_accessor :version, :hlen, :tos, :id, :length, :frag,
|
8
|
+
:ttl, :dst, :proto, :cksum, :src, :saddr, :daddr, :data
|
9
|
+
def initialize(proto=Socket::IPPROTO_ICMP, src='0.0.0.0', dst=nil)
|
10
|
+
@version = 4
|
11
|
+
@hlen = 5
|
12
|
+
@tos = 0
|
13
|
+
@length = 20
|
14
|
+
@id = $$
|
15
|
+
@frag = 0
|
16
|
+
@ttl = 255
|
17
|
+
@proto = proto
|
18
|
+
@cksum = 0
|
19
|
+
@src = src
|
20
|
+
@saddr = Socket.inet_aton(src)
|
21
|
+
@dst = dst || '0.0.0.0'
|
22
|
+
@daddr = Socket.inet_aton(@dst)
|
23
|
+
@data = ''
|
24
|
+
end
|
25
|
+
|
26
|
+
def assemble
|
27
|
+
header = [(@version & 0x0f) << 4 | (@hlen & 0x0f),
|
28
|
+
@tos, @length + @data.size,
|
29
|
+
Socket.htons(@id), @frag,
|
30
|
+
@ttl, @proto
|
31
|
+
].pack('CCSSSCC')
|
32
|
+
header + "\000\000" + @saddr.to_s + @daddr.to_s + @data
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.disassemble data
|
36
|
+
ip = Iphdr.new
|
37
|
+
pkt = data[0..11].unpack('CCnnnCCn')
|
38
|
+
ip.version = (pkt.first >> 4 & 0x0f)
|
39
|
+
ip.hlen = (pkt.first & 0x0f)
|
40
|
+
ip.tos, ip.length, ip.id, ip.frag, ip.ttl, ip.proto, ip.cksum = pkt[1..-1]
|
41
|
+
ip.saddr = data[12..15]
|
42
|
+
ip.daddr = data[16..19]
|
43
|
+
ip.src = Socket.inet_ntoa(ip.saddr)
|
44
|
+
ip.dst = Socket.inet_ntoa(ip.daddr)
|
45
|
+
ip
|
46
|
+
end
|
47
|
+
end
|
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: emtraceroute
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- kubo39
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-10-01 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: eventmachine
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.0.0.beta.3
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.0.0.beta.3
|
30
|
+
description: traceroute utility on EventMachine
|
31
|
+
email: kubo39@gmail.com
|
32
|
+
executables:
|
33
|
+
- traceroute
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- .gitignore
|
38
|
+
- Gemfile
|
39
|
+
- LICENSE.txt
|
40
|
+
- README.md
|
41
|
+
- Rakefile
|
42
|
+
- bin/traceroute
|
43
|
+
- emtraceroute.gemspec
|
44
|
+
- lib/emtraceroute.rb
|
45
|
+
- lib/emtraceroute/ext/socket.rb
|
46
|
+
- lib/emtraceroute/handler.rb
|
47
|
+
- lib/emtraceroute/hop.rb
|
48
|
+
- lib/emtraceroute/icmp.rb
|
49
|
+
- lib/emtraceroute/ip.rb
|
50
|
+
- lib/emtraceroute/version.rb
|
51
|
+
homepage: https://github.com/kubo39/emtraceroute
|
52
|
+
licenses: []
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options: []
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ! '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
requirements: []
|
70
|
+
rubyforge_project:
|
71
|
+
rubygems_version: 1.8.24
|
72
|
+
signing_key:
|
73
|
+
specification_version: 3
|
74
|
+
summary: traceroute utility on EventMachine
|
75
|
+
test_files: []
|