emtraceroute 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ Gemfile.lock
2
+ *.gem
3
+ *~
4
+ pkg
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in emtraceroute.gemspec
4
+ gemspec
5
+
6
+ gem 'eventmachine'
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
@@ -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,14 @@
1
+ # extension for socket library
2
+ class Socket
3
+ def self.inet_aton ip
4
+ ip.split(/\./).map(&:to_i).pack("C*")
5
+ end
6
+
7
+ def self.inet_ntoa n
8
+ n.unpack("C*").join "."
9
+ end
10
+
11
+ def self.htons id
12
+ [id].pack("s").unpack("n").first.to_i
13
+ end
14
+ 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
@@ -0,0 +1,5 @@
1
+ module EM
2
+ module Traceroute
3
+ VERSION = '0.0.2'
4
+ end
5
+ 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: []