├── README.md ├── vp_config.rb ├── prober.rb └── vantage_point.rb /README.md: -------------------------------------------------------------------------------- 1 | uw-prober 2 | ========================= 3 | 4 | Network measurement helpers. Data collected from them are stored at gs://m-lab_revtr/ 5 | 6 | Files will are uploaded as revtr_$CURR_DATE.txt, where $CURR_DATE is the result of `date +%F`. Each line is a comma-separated list of values for a reverse traceroute measurement. The fields are described below, and are generated from the table at the end of the email. 7 | 8 | - Destination (int IP) [This is where we are measuring a reverse path from.] 9 | - Source (int IP) 10 | - Date (string timestamp) 11 | - Hops (int IP) [There are thirty entries. If the value is 0, there are two cases: If this occurs between two non-zero values, there is no response from the hop. If a sequence of 0's occurs as the end of a measurement, they are unused entries. This occurs because we used a fixed-size table in the database.) 12 | - Hop type (int) [There are thirty entries; the first hop type corresponds to the first IP in the above 'Hops' section. The hop types are listed below.] 13 | 14 | Types are: 15 | Record Route = 1 16 | Spoofed Record Route = 2 17 | Prespecified Timestamp = 3 18 | Spoofed Prespecified Timestamp= 4 19 | Spoofed Prespecified Timestamp with Zero Stamp = 5 20 | Spoofed Prespecified Timestamp with Zero Stamp Double Stamp = 6 21 | Assumed Symmetry from Forward Traceroute = 7 22 | Intersection from Traceroute toward Source = 8 23 | Destination hop = 9 24 | 25 | The last four fields are not statistical information and you can ignore them. 26 | -------------------------------------------------------------------------------- /vp_config.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2009, University of Washington 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # * Redistributions of source code must retain the above copyright 7 | # notice, this list of conditions and the following disclaimer. 8 | # * Redistributions in binary form must reproduce the above copyright 9 | # notice, this list of conditions and the following disclaimer in the 10 | # documentation and/or other materials provided with the distribution. 11 | # * Neither the name of the University of Washington nor the 12 | # names of its contributors may be used to endorse or promote products 13 | # derived from this software without specific prior written permission. 14 | # 15 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | # DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 19 | # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | require 'yaml' 26 | require 'open-uri' 27 | 28 | module ProberConfig 29 | 30 | # URI to fetch current controller port from 31 | CONTROLLER_INFO="http://revtr.cs.washington.edu/vps/registrar.txt" 32 | 33 | # path of proberoute probing tools 34 | DEFAULT_PROBEROUTE_DIR="./" 35 | 36 | # path to put temporary output 37 | DEFAULT_TMP_OUTPUT_DIR="/tmp/" 38 | 39 | # default port for DRb 40 | DEFAULT_PORT=54321 41 | 42 | # number of threads to use in probing tools 43 | PROBING_THREADS = 40 44 | counter = 0 45 | begin 46 | 47 | vp_name = `hostname`.strip 48 | rate2vp = YAML::load(open('http://revtr.cs.washington.edu/vps/RateLimit.txt')) 49 | 50 | if rate2vp.include?(vp_name) 51 | PROBING_THREADS = rate2vp[vp_name].to_i 52 | end 53 | 54 | rescue 55 | 56 | counter += 1 57 | 58 | if counter < 3 59 | sleep 2 60 | retry 61 | end 62 | 63 | end 64 | 65 | TEST_IP="128.208.2.159" 66 | end 67 | 68 | $LOG_FILE="/tmp/vp_log.txt" 69 | $VP_ERROR=2 70 | 71 | module VantagePointConfig 72 | VP_CONFIG_VERSION = "$Id: vp_config.rb,v 1.5 2012/08/02 14:50:03 revtr Exp $".split(" ").at(2).to_f 73 | 74 | 75 | end 76 | -------------------------------------------------------------------------------- /prober.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2009, University of Washington 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # * Redistributions of source code must retain the above copyright 7 | # notice, this list of conditions and the following disclaimer. 8 | # * Redistributions in binary form must reproduce the above copyright 9 | # notice, this list of conditions and the following disclaimer in the 10 | # documentation and/or other materials provided with the distribution. 11 | # * Neither the name of the University of Washington nor the 12 | # names of its contributors may be used to endorse or promote products 13 | # derived from this software without specific prior written permission. 14 | # 15 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | # DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 19 | # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | # wraps our probing tools in an object 26 | 27 | require 'drb' 28 | require 'drb/acl' 29 | require 'net/http' 30 | require 'socket' 31 | require "vp_config.rb" 32 | 33 | class Prober 34 | # allows controller to call back to issue measurements, etc 35 | 36 | include DRbUndumped 37 | include ProberConfig 38 | 39 | PROBER_VERSION = "$Id: prober.rb,v 1.13 2011/07/13 22:29:54 ethan Exp $".split(" ").at(2).to_f 40 | 41 | def Prober::uri2host(uri) 42 | uri.chomp("\n").split("/").at(-1).split(":").at(0) 43 | end 44 | 45 | # build an acl from a uri allowing only connections from localhost and the 46 | # host of the uri 47 | def Prober::build_acl(uri) 48 | ACL.new(["deny","all","allow","localhost","allow","127.0.0.1","allow",Prober::uri2host(uri)]) 49 | end 50 | 51 | # fetch the default controller uri 52 | # throws an exception if the uri is obviously not proper 53 | def Prober::get_default_controller_uri 54 | controller_uri = Net::HTTP.get_response(URI.parse(Prober::CONTROLLER_INFO)).body.chomp("\n") 55 | if not controller_uri[0..7]=="druby://" 56 | raise DRb::DRbBadURI, "Bad controller uri: #{controller_uri}", caller 57 | end 58 | controller_uri 59 | end 60 | 61 | # takes the directory to look for probing tools in 62 | # if nil, then looks in Prober::DEFAULT_PROBEROUTE_DIR 63 | # optionally, takes a path to store temp output in 64 | # if DRb service is running, @drb should be set 65 | # so @drb.nil? can be used to check if it is running 66 | def initialize(proberoute_dir,output_dir=Prober::DEFAULT_TMP_OUTPUT_DIR) 67 | @drb=nil 68 | @counter_mutex=Mutex.new 69 | @count=0 70 | @device=self.set_device 71 | if proberoute_dir.nil? 72 | @proberoute_dir=Prober::DEFAULT_PROBEROUTE_DIR 73 | else 74 | @proberoute_dir=proberoute_dir 75 | end 76 | @output_dir=output_dir 77 | killall_receive 78 | end 79 | attr_reader :device, :drb 80 | attr_writer :device 81 | 82 | # whether an upgrade to this version from an earlier one requires a 83 | # restart 84 | # should be set to false, unless a particular version needs it 85 | # doing it as a method in case we ever need more logic in it 86 | def restart_prober? 87 | false 88 | end 89 | 90 | def uri 91 | return nil if @drb.nil? 92 | return @drb.uri 93 | end 94 | 95 | def set_device 96 | @device="eth0" 97 | current_packets=0 98 | `cat /proc/net/dev |grep eth`.each_line{|interface_stats| 99 | device=interface_stats.chomp("\n").strip.split(":").at(0) 100 | next if not device[0..2]=="eth" 101 | packets=interface_stats.chomp("\n").strip.split(":").at(1).strip.split(" ").at(1).to_i 102 | if packets>current_packets 103 | @device, current_packets = device, packets 104 | end 105 | } 106 | @device 107 | end 108 | 109 | # acl may be nil 110 | # front_prober is whether to front the prober on the DRb (returning it via 111 | # the URI), or just to start up the service to allow connections out 112 | # if successful, will set @drb 113 | def start_service(acl,front_prober,port=Prober::DEFAULT_PORT) 114 | front = ( front_prober ? self : nil ) 115 | begin 116 | hostname=Socket::gethostname 117 | if hostname.include?("measurement-lab.org") 118 | hostname=UDPSocket.open {|s| s.connect(Prober::TEST_IP, 1); s.addr.last} 119 | end 120 | uri="druby://#{hostname}:#{port}" 121 | @drb=(DRb.start_service uri, front, acl) 122 | rescue 123 | begin 124 | $stderr.puts "Did not work on #{uri} port, retrying nil port" 125 | @drb=(DRb.start_service nil, front, acl) 126 | rescue 127 | uri="druby://#{hostname}:#{50000+rand(15000)}" 128 | $stderr.puts "Did not work on nil port, retrying #{uri}" 129 | @drb=(DRb.start_service uri, front, acl) 130 | end 131 | end 132 | self.log("Started on #{@drb.uri}") 133 | end 134 | 135 | def stop_service 136 | self.log("Stopping DRb service") 137 | begin 138 | if not @drb.nil? 139 | @drb.stop_service 140 | @drb=nil 141 | end 142 | rescue 143 | self.log($!.to_s) 144 | end 145 | end 146 | 147 | def shutdown(code=0) 148 | self.log "Exiting: Received shutdown message #{code}" 149 | Thread.new{ 150 | sleep 1 151 | Kernel.exit(code) 152 | } 153 | end 154 | 155 | def log(msg,except=nil) 156 | $stderr.puts `date +%Y/%m/%d.%H%M.%S`.chomp("\n") + " " + msg + (except.nil? ? "" : " #{except.class} #{except.to_s}") 157 | end 158 | 159 | def hostname 160 | if self.uri.nil? 161 | begin 162 | Socket::gethostname 163 | rescue 164 | UDPSocket.open {|s| s.connect('128.208.2.159', 1); s.addr.last } 165 | end 166 | else 167 | Prober::uri2host(self.uri) 168 | end 169 | end 170 | 171 | def port 172 | (self.uri.nil?) ? nil : self.uri.chomp("\n").split("/").at(-1).split(":").at(1) 173 | end 174 | 175 | def remove_files(*files) 176 | files.each{|f| 177 | begin 178 | if File.exist?(f) 179 | File.delete(f) 180 | end 181 | rescue 182 | self.log "Exception: Can't delete file #{$!.to_s}" 183 | end 184 | } 185 | end 186 | 187 | # create an appropriately named target file 188 | # file_writer is a block that take the file and properly "fills" it 189 | # return the name of the file 190 | def create_target_file( &file_writer ) 191 | uid=0 192 | @counter_mutex.synchronize do 193 | uid=@count 194 | @count += 1 195 | end 196 | fn="#{@output_dir}/targs_#{self.port}_#{uid}.txt" 197 | File.open( fn, File::CREAT|File::TRUNC|File::WRONLY|File::APPEND) {|targf| 198 | file_writer.call(targf) 199 | } 200 | return fn 201 | end 202 | 203 | def traceroute( targs ) 204 | self.log "Sending #{targs.length} traceroutes" 205 | fn=create_target_file{|targ_file| targ_file.puts targs.join("\n")} 206 | `sudo #{@proberoute_dir}/randstprober #{Prober::PROBING_THREADS} #{fn} #{@device} 1 #{fn}.trace.out #{fn}.count.out` 207 | `sudo chown \`id -u\` #{fn}.trace.out #{fn}.count.out` 208 | f=nil 209 | begin 210 | f=File.new("#{fn}.trace.out") 211 | probes=f.read 212 | self.remove_files("#{fn}.trace.out", fn, "#{fn}.count.out") 213 | return probes 214 | ensure 215 | f.close unless f.nil? 216 | end 217 | end 218 | 219 | def paristrace(target) 220 | self.log "Running Paris traceroute towards #{target}" 221 | fn = "#{@output_dir}/paristrace_output_#{self.port}_#{target}" 222 | self.log "sudo #{@proberoute_dir}/ptrun/ptrun.py -d #{target} -o #{fn}.bin -s #{fn}.txt" 223 | `cd #{@proberoute_dir}/ptrun && sudo ./ptrun.py -d #{target} -o #{fn}.bin -s #{fn}.txt` 224 | `sudo chown $(id -u) #{fn}.bin #{fn}.txt` 225 | hops = IO.read("#{fn}.txt") 226 | remove_files("#{fn}.txt", "#{fn}.bin") 227 | return hops 228 | end 229 | 230 | def ping( targs ) 231 | self.log "Sending #{targs.length} pings" 232 | fn=create_target_file{|targ_file| targ_file.puts targs.join("\n")} 233 | probes=`sudo #{@proberoute_dir}/aliasprobe #{Prober::PROBING_THREADS} #{fn} #{@device}` 234 | self.remove_files(fn) 235 | return probes 236 | end 237 | 238 | def rr( targs ) 239 | self.log "Sending #{targs.length} record-routes" 240 | fn=create_target_file{|targ_file| targ_file.puts targs.join("\n")} 241 | `sudo #{@proberoute_dir}/rrping #{Prober::PROBING_THREADS} #{fn} #{@device} #{fn}.rrping.out` 242 | `sudo chown \`id -u\` #{fn}.rrping.out #{fn}.rrping.out.ttl` 243 | f=nil 244 | begin 245 | f=File.new("#{fn}.rrping.out") 246 | probes=f.read 247 | self.remove_files(fn,"#{fn}.rrping.out","#{fn}.rrping.out.ttl") 248 | return probes 249 | ensure 250 | f.close unless f.nil? 251 | end 252 | end 253 | 254 | def ts( probes ) 255 | self.log "Sending #{probes.length} timestamps" 256 | fn=create_target_file{|targ_file| probes.each{|probe| targ_file.puts probe.join(" ")}} 257 | results=`sudo #{@proberoute_dir}/tsprespec-ping #{Prober::PROBING_THREADS} #{fn} #{@device}` 258 | self.remove_files(fn) 259 | return results 260 | end 261 | 262 | # probes is hash[receiver] -> [dst1, dst2,...] 263 | # for some reason, the m-lab nodes don't like it if i iterate through the 264 | # hash as a hash... they try to make an RPC call to themselves, which 265 | # fails bc it is using the hostname, not the IP 266 | # so instead, treat the keys as an array 267 | def spoof_rr(probes, id=45678) 268 | fn=create_target_file{|targf| 269 | probes.keys.each{|rec| 270 | dsts=probes[rec] 271 | self.log "Spoofing #{rec} for #{dsts.length} record-routes" 272 | dsts.each{|d| 273 | targf.puts d + " " + rec 274 | } 275 | } 276 | } 277 | `sudo #{@proberoute_dir}/rrspoof #{Prober::PROBING_THREADS} #{fn} #{@device} #{id}` 278 | self.remove_files(fn) 279 | end 280 | 281 | # probes is hash[receiver] -> [[dst1,ts1,ts2,...], [dst2,ts3,ts4,...],...] 282 | # for some reason, the m-lab nodes don't like it if i iterate through the 283 | # hash as a hash... they try to make an RPC call to themselves, which 284 | # fails bc it is using the hostname, not the IP 285 | # so instead, treat the keys as an array 286 | def spoof_ts(probes, id=45678) 287 | fn=create_target_file{|targf| 288 | probes.keys.each{|rec| 289 | tss=probes[rec] 290 | self.log "Spoofing #{rec} for #{tss.length} timestamps" 291 | tss.each{|ts| 292 | targf.puts rec + " " + ts.join(" ") 293 | } 294 | } 295 | } 296 | `sudo #{@proberoute_dir}/tsprespec-spoof #{Prober::PROBING_THREADS} #{fn} #{@device} #{id}` 297 | self.remove_files(fn) 298 | end 299 | 300 | def receive_spoofed_probes(id, type=:rrspoof) 301 | uid=0 302 | `sudo pkill -9 -f "(tsprespec|rrspoof)-recv [^\s]+ [^\s]+ #{id}"` 303 | @counter_mutex.synchronize do 304 | uid=@count 305 | @count += 1 306 | end 307 | fn="#{@output_dir}/spoof_#{self.port}_#{uid}_#{type}ping.out" 308 | # CUNHA parallelization: commented the following line 309 | # `sudo killall -9 -e #{type}-recv 1> /dev/null 2>&1` 310 | self.log "Receiving spoofed #{type} #{fn}" 311 | Thread.new{`sudo #{@proberoute_dir}/#{type}-recv #{@device} #{fn} #{id}`} 312 | return fn 313 | end 314 | 315 | def receive_spoofed_rr(id=45678) 316 | receive_spoofed_probes(id, :rrspoof) 317 | end 318 | 319 | def receive_spoofed_ts(id=45678) 320 | receive_spoofed_probes(id, :tsprespec) 321 | end 322 | 323 | # kill all receive commands, to get rid of zombies 324 | def killall_receive 325 | `sudo killall -9 -e tsprespec-recv 1> /dev/null 2>&1` 326 | `sudo killall -9 -e rrspoof-recv 1> /dev/null 2>&1` 327 | end 328 | 329 | # kill receivers and retrieve results 330 | # does killall at the moment - makes it not safe to make multiple receive 331 | # calls at once 332 | def kill_and_retrieve(fid, id=45678) 333 | # CUNHA paralellization: changed kill commands 334 | # `sudo killall -9 -e tsprespec-recv 1> /dev/null 2>&1` 335 | # `sudo killall -9 -e rrspoof-recv 1> /dev/null 2>&1` 336 | `sudo pkill -9 -f "(tsprespec|rrspoof)-recv [^\s]+ [^\s]+ #{id}"` 337 | f=nil 338 | begin 339 | self.log "Retrieving #{fid}" 340 | f=File.new(fid) 341 | probes=f.read 342 | sources=IO.readlines(fid + ".src").collect{|x| x.chomp("\n")} 343 | `sudo chown \`id -u\` #{fid} #{fid}.src` 344 | to_remove=[fid,"#{fid}.src"] 345 | begin 346 | if File.exist?("#{fid}.ttl") 347 | `sudo chown \`id -u\` #{fid}.ttl` 348 | to_remove << "#{fid}.ttl" 349 | end 350 | rescue 351 | end 352 | self.remove_files(*to_remove) 353 | return [probes,sources] 354 | ensure 355 | f.close unless f.nil? 356 | end 357 | end 358 | end 359 | 360 | -------------------------------------------------------------------------------- /vantage_point.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2009, University of Washington 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # * Redistributions of source code must retain the above copyright 7 | # notice, this list of conditions and the following disclaimer. 8 | # * Redistributions in binary form must reproduce the above copyright 9 | # notice, this list of conditions and the following disclaimer in the 10 | # documentation and/or other materials provided with the distribution. 11 | # * Neither the name of the University of Washington nor the 12 | # names of its contributors may be used to endorse or promote products 13 | # derived from this software without specific prior written permission. 14 | # 15 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | # DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 19 | # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | #! /usr/bin/ruby 27 | require 'net/http' 28 | require 'optparse' 29 | require 'resolv' 30 | 31 | require "vp_config.rb" 32 | 33 | begin 34 | require 'prober' 35 | rescue LoadError 36 | # lets us bootstrap the initial version that broke out prober.rb 37 | begin 38 | $stderr.puts `date +%Y/%m/%d.%H%M.%S`.chomp("\n") + " " + "Unable to load prober: #{$!.class} #{$!.to_s}" 39 | $stderr.puts `date +%Y/%m/%d.%H%M.%S`.chomp("\n") + " " + "Trying to download prober source." 40 | source = Net::HTTP.get_response(URI.parse('http://revtr.cs.washington.edu/vps/prober.rb')).body 41 | $stderr.puts `date +%Y/%m/%d.%H%M.%S`.chomp("\n") + " " + "Preparing to write prober.rb" 42 | File.open("prober.rb", File::CREAT|File::TRUNC|File::WRONLY, 0644){|f| 43 | f.puts source 44 | } 45 | require 'prober' 46 | rescue LoadError,RuntimeError 47 | $stderr.puts `date +%Y/%m/%d.%H%M.%S`.chomp("\n") + " " + "EXITING: Unable to load prober: #{$!.class} #{$!.to_s}" 48 | Kernel.exit(6) 49 | end 50 | end 51 | 52 | # issues: race condition on the target file, always named the same 53 | # should kill by pid, maybe, instead of killall 54 | # - have a thread that registers, then goes to sleep 55 | # - rrping always creates the same output file, so do some of the others, a 56 | # race unless i run rrping from a new directory 57 | # actually, seems like it might be based on where you start vantage_point.rb 58 | # can do $? to get exit status of system calls. should i be returning this to 59 | # the user too? 60 | 61 | 62 | # if the uid used for spoofed tr is out of range 63 | class OutOfRangeError < RuntimeError 64 | attr_reader :min, :max, :value 65 | def initialize(min, max,value) 66 | @min,@max,@value=min,max,value 67 | end 68 | 69 | def to_s 70 | super + ": #{@value} not in (#{@min}..#{@max})" 71 | end 72 | end 73 | 74 | class VantagePoint < Prober 75 | # allows controller to call back to issue measurements, etc 76 | include DRbUndumped 77 | VP_VERSION = "$Id: vantage_point.rb,v 1.81 2012/08/02 14:38:50 revtr Exp $".split(" ").at(2).to_f 78 | 79 | include VantagePointConfig 80 | UPDATE_INFO = { 81 | :prober => { 82 | :fn => 'prober.rb', 83 | :curr_source => URI.parse('http://revtr.cs.washington.edu/vps/prober.rb'), 84 | :curr_version => URI.parse('http://revtr.cs.washington.edu/vps/prober_version.txt'), 85 | :version => PROBER_VERSION 86 | }, 87 | :vp => { 88 | :fn => 'vantage_point.rb', 89 | :curr_source => URI.parse('http://revtr.cs.washington.edu/vps/vantage_point.rb'), 90 | :curr_version => URI.parse('http://revtr.cs.washington.edu/vps/vantage_point_version.txt'), 91 | :version => VP_VERSION 92 | }, 93 | :vp_config => { 94 | :fn => 'vp_config.rb', 95 | :curr_source => URI.parse('http://revtr.cs.washington.edu/vps/vp_config.rb'), 96 | :curr_version => URI.parse('http://revtr.cs.washington.edu/vps/vp_config_version.txt'), 97 | :version => VP_CONFIG_VERSION 98 | } 99 | } 100 | 101 | # if controller_uri is nil, will fetch from the default location 102 | # if not using an acl, will start_service no matter what 103 | # if using acl, will start service if @controller_uri is assigned without 104 | # exceptions being thrown 105 | def initialize(controller_uri,use_acl,front_prober,port,proberoute_dir,output_dir=Prober::DEFAULT_TMP_OUTPUT_DIR) 106 | super(proberoute_dir,output_dir) 107 | @pid2fn = {} 108 | @front_prober=front_prober 109 | @port=port 110 | # setting in case we can't get the controller uri below 111 | # and it doesn't get set properly 112 | # need it set to something true so we know to use acl in the 113 | # future 114 | @acl=use_acl 115 | @controller_uri=nil 116 | begin 117 | if controller_uri.nil? 118 | controller_uri = Prober::get_default_controller_uri 119 | end 120 | rescue 121 | self.log("Unable to fetch controller uri:",$!) 122 | end 123 | begin 124 | if controller_uri.nil? 125 | self.register 126 | else 127 | update_controller(controller_uri) 128 | end 129 | rescue 130 | self.log("Unable to register:",$!) 131 | end 132 | end 133 | 134 | attr_reader :controller_uri 135 | 136 | # starts_service if not started already (unless we are using an ACL AND we 137 | # don't have a controller uri) 138 | # may throw exceptions 139 | # returns nil if successful, else raises an exception 140 | def register(controller=nil) 141 | if self.drb.nil? 142 | if not @acl 143 | self.start_service(nil,@front_prober,@port) 144 | elsif @controller_uri 145 | @acl=Prober::build_acl(@controller_uri) 146 | self.start_service(@acl,@front_prober,@port) 147 | else 148 | self.log("Not starting service: ACL required, but no controller uri") 149 | end 150 | end 151 | if controller.nil? 152 | if @controller_uri.nil? 153 | raise RuntimeError, "Missing URI for controller", caller 154 | end 155 | controller=DRbObject.new nil, @controller_uri 156 | end 157 | controller.register(self) 158 | return nil 159 | end 160 | 161 | # may throw exceptions. 162 | def unregister 163 | if @controller_uri 164 | (DRbObject.new nil, @controller_uri).unregister(self) 165 | end 166 | end 167 | 168 | def stop_service 169 | begin 170 | self.unregister 171 | rescue 172 | self.log("Unable to unregister when stopping service: #{$!.class} #{$!}\n#{$!.backtrace.join("\n")}") 173 | ensure 174 | super 175 | end 176 | end 177 | 178 | def version 179 | return VantagePoint::VP_VERSION 180 | end 181 | 182 | # if no controller, don't update 183 | # if same controller, don't update 184 | # if new controller, try to unregister, then register with new one 185 | def update_controller(uri) 186 | if uri.nil? or uri.length==0 187 | self.log "Not updating controller: nil" 188 | return 189 | end 190 | 191 | if uri!=@controller_uri 192 | self.log "Updating from controller #{@controller_uri} to #{uri}" 193 | if not @controller_uri.nil? 194 | begin 195 | # if we need to build a new ACL, we have to stop the service first 196 | if (@acl && Prober::uri2host(uri)!=Prober::uri2host(@controller_uri)) 197 | self.log("Stopping service for new ACL\n#{ Prober::build_acl(uri)}\n#{self.drb.config[:tcp_acl]}") 198 | self.stop_service 199 | else 200 | self.log("Unregistering for new controller") 201 | self.unregister 202 | end 203 | rescue 204 | self.log "Exception: Unable to unregister from #{@controller_uri} #{$!.to_s}", $VP_ERROR 205 | end 206 | end 207 | @controller_uri=uri 208 | self.register 209 | end 210 | end 211 | 212 | $RESTART=12 213 | $UPGRADE_RESTART=11 214 | # lets us kill it remotely 215 | # overrides Prober.shutdown to include the upgrade options 216 | # but is that right, or should it unregister while shutting down? 217 | def shutdown(code=0) 218 | self.log "Exiting: Received shutdown message #{code}" 219 | # begin 220 | # if code==$UPGRADE_RESTART 221 | # self.log "Upgrading, so not unregistering" 222 | # elsif code==$RESTART 223 | # self.log "Restarting without upgrade" 224 | # else 225 | # self.unregister 226 | # end 227 | # ensure 228 | super.shutdown(code) 229 | # end 230 | end 231 | 232 | def restart 233 | Kernel::system("sudo /usr/sbin/atd; echo \"sleep 10; ./vantage_point.rb 1>> #{$LOG_FILE} 2>&1 \"|at now") 234 | sleep 2 235 | self.log "Shutting down for restart" 236 | self.shutdown($RESTART) 237 | 238 | end 239 | 240 | # whether an upgrade to this version from an earlier one requires a 241 | # restart 242 | # should be set to false, unless a particular version needs it 243 | # doing it as a method in case we ever need more logic in it 244 | def restart_vp? 245 | true 246 | end 247 | 248 | 249 | # returns true if it updated 250 | def check_for_update(component=:vp) 251 | updated=false 252 | begin 253 | self.log "Checking for newer version of #{UPDATE_INFO[component][:fn]}...." 254 | current_version = Net::HTTP.get_response(UPDATE_INFO[component][:curr_version]).body.to_f 255 | if current_version==0.0 256 | self.log "Error: Unable to fetch number of current version" 257 | # we check != rather than > bc w cvs versions, 1.2 comes before 1.11 258 | # assumption is that the webpage will always have the version we want 259 | # to run 260 | elsif current_version!=UPDATE_INFO[component][:version] 261 | self.log "Upgrading from #{UPDATE_INFO[component][:version]} to #{current_version}" 262 | code = Net::HTTP.get_response(UPDATE_INFO[component][:curr_source]).body 263 | self.log "Preparing to write version #{current_version}" 264 | File.open(UPDATE_INFO[component][:fn], File::CREAT|File::TRUNC|File::WRONLY, 0755){|f| 265 | f.puts code 266 | } 267 | self.log "Wrote version #{current_version}" 268 | load(UPDATE_INFO[component][:fn]) 269 | load(UPDATE_INFO[:vp_config][:fn]) 270 | updated=true 271 | UPDATE_INFO[component][:version]=current_version 272 | self.log "Upgraded to version #{UPDATE_INFO[component][:version]}" 273 | # sleep 10 to give this one a chance to shut down - only 274 | # one can run at a time 275 | # Kernel::system("sudo /usr/sbin/atd; echo \"sleep 10; ./vantage_point.rb 1>> #{$LOG_FILE} 2>&1 \"|at now") 276 | # self.log "Shutting down for upgrade to version #{current_version}" 277 | # sleep 2 278 | # self.shutdown($UPGRADE_RESTART) 279 | else 280 | self.log "No upgrade available from #{UPDATE_INFO[component][:version]}" 281 | end 282 | rescue 283 | self.log "Unable to check for update: #{$!.class}: #{$!.to_s}\n#{$!.backtrace.join("\n")}" 284 | end 285 | return updated 286 | end 287 | 288 | 289 | # execute a system call, return stdout 290 | def backtic(cmd) 291 | self.log "VantagePoint::backtic: running \`#{cmd}\`" 292 | return Kernel::`(cmd) 293 | # ` 294 | end 295 | 296 | # not positive this always works correctly with 3+ params 297 | def system(cmd,*params) 298 | self.log "VantagePoint::system: running \"#{cmd} #{params.join(" ")}\"" 299 | return Kernel::system(cmd,*params) 300 | end 301 | 302 | def get_results( pid ) 303 | files = @pid2fn[pid] 304 | files.each { |file| `sudo chown \`id -u\` #{file}` } 305 | output = `if [ \`ps -p #{pid} | grep -c #{files.at(0)}\` == 0 ] ; then echo \"process completed\" >> #{$LOG_FILE};\ else echo \"killing pid #{pid}\" >> #{$LOG_FILE} ; kill -9 #{pid} ; fi` 306 | f=nil 307 | probes=nil 308 | begin 309 | f=File.new(files.at(0)) 310 | probes=f.read 311 | self.remove_files(*files) 312 | return probes 313 | ensure 314 | f.close unless f.nil? 315 | end 316 | end 317 | 318 | def launch_traceroute( targs ) 319 | fn=create_target_file{|targ_file| targ_file.puts targs.join("\n")} 320 | pid=IO.popen("sudo /usr/sbin/atd; echo \"sudo #{@proberoute_dir}/randstprober #{Prober::PROBING_THREADS} #{fn} #{@device} 1 #{fn}.trace.out #{fn}.count.out\" | at now").pid 321 | @pid2fn[pid]=["#{fn}.trace.out", "#{fn}.count.out", fn] 322 | return pid 323 | end 324 | 325 | def launch_ping( targs ) 326 | fn=create_target_file{|targ_file| targ_file.puts targs.join("\n")} 327 | pid=IO.popen("sudo /usr/sbin/atd; echo \"sudo #{@proberoute_dir}/aliasprobe #{Prober::PROBING_THREADS} #{fn} #{@device} > #{fn}.out\" | at now").pid 328 | @pid2fn[pid]=["#{fn}.out", fn] 329 | return pid 330 | end 331 | 332 | def launch_rr( targs ) 333 | fn=create_target_file{|targ_file| targ_file.puts targs.join("\n")} 334 | pid=IO.popen("sudo /usr/sbin/atd; echo \"sudo #{@proberoute_dir}/rrping #{Prober::PROBING_THREADS} #{fn} #{@device} #{fn}.rrping.out\" | at now").pid 335 | @pid2fn[pid]=["#{fn}.rrping.out", "#{fn}.rrping.out.ttl",fn] 336 | return pid 337 | end 338 | 339 | def launch_ts( probes ) 340 | fn=create_target_file{|targ_file| probes.each{|probe| targ_file.puts probe.join(" ")}} 341 | pid=IO.popen("sudo /usr/sbin/atd; echo \"sudo #{@proberoute_dir}/tsprespec-ping #{Prober::PROBING_THREADS} #{fn} #{@device} > #{fn}.out\" | at now").pid 342 | @pid2fn[pid]=["#{fn}.out", fn] 343 | return pid 344 | end 345 | 346 | # probes is hash[receiver] -> [tr1, tr2, ...] 347 | # tr is either dst, [dst], [dst, start_ttl], or [dst, start_ttl, end_ttl] 348 | # if ttls aren't given, will do 1..30 349 | # id is the spoofer ID to put into the probes 350 | # must be at most 11 bits 351 | def spoof_tr(probes, id) 352 | if id>2047 or id<0 353 | raise OutOfRangeError.new(0,2047,id), "Spoofer ID out of range" 354 | end 355 | fn=create_target_file{|targf| 356 | probes.keys.each{|rec| 357 | trs=probes[rec] 358 | $stderr.puts "sending spoofed traceroute ID=#{id} as #{rec} " + trs.collect{|x| x.join(",")}.join(" ") 359 | trs.each{|tr| 360 | case tr 361 | when Array 362 | dst=tr.at(0) 363 | case tr.length 364 | when 1 365 | start,finish=1,30 366 | when 2 367 | start,finish=tr.at(1),30 368 | else 369 | start,finish=tr.at(1),tr.at(2) 370 | end 371 | when String 372 | dst,start,finish=tr,1,30 373 | end 374 | if start<1 or start>finish 375 | raise OutOfRangeError.new(1,finish,start), "Start TTL out of range" 376 | end 377 | if finish>31 378 | raise OutOfRangeError.new(start,31,finish), "Finish TTL out of range" 379 | end 380 | (start..finish).each{|ttl| 381 | targf.puts "#{dst} #{ttl} #{rec}" 382 | } 383 | } 384 | } 385 | } 386 | `sudo #{@proberoute_dir}/pingspoof #{Prober::PROBING_THREADS} #{fn} #{@device} #{id}` 387 | self.remove_files(fn) 388 | end 389 | end 390 | 391 | # this lets us load in new versions of the class without rerunning the code 392 | # below 393 | if not $started 394 | $started=true 395 | 396 | $stderr.puts "Version number #{VantagePoint::VP_VERSION}" 397 | if `ps aux|grep vantage_point.rb|grep ruby|grep -v grep|wc -l`.to_i>1 398 | $stderr.puts "Already running" 399 | $stderr.puts `ps aux|grep vantage_point.rb ` 400 | Kernel.exit(17) 401 | end 402 | 403 | 404 | # Hash will include options parsed by OptionParser. 405 | options = Hash.new 406 | 407 | optparse = OptionParser.new do |opts| 408 | options[:acl] = true 409 | opts.on( '-A', '--no-acl', 'Disable access control list. Otherwise, only allows connections from localhost and from the host specified in the controller URI.' ) do 410 | options[:acl] = false 411 | end 412 | options[:controller_uri] = nil 413 | opts.on( '-cURI', '--controller=URI', "Controller URI (default={fetch from #{Prober::CONTROLLER_INFO}})") do|f| 414 | options[:controller_uri] = f 415 | end 416 | options[:backoff] = true 417 | opts.on( '-d', '--destination-probing', 'Enable destination probing. Overrides default assumption that first hop from destination symmetric.' ) do 418 | options[:backoff] = false 419 | end 420 | options[:front] = true 421 | opts.on( '-F', '--no-front', 'Do not front the prober with DRb.' ) do 422 | options[:front] = false 423 | end 424 | opts.on( '-h', '--help', 'Display this screen' ) do 425 | puts("Usage: #{$0} [OPTION]... DESTINATION1 [DESTINATION2]...") 426 | puts opts.to_s.split("\n")[1..-1].join("\n") 427 | exit 428 | end 429 | options[:output_dir] = Prober::DEFAULT_TMP_OUTPUT_DIR 430 | opts.on( '-oPATH', '--out=PATH', "Temp output path PATH (default=#{Prober::DEFAULT_TMP_OUTPUT_DIR})") do|f| 431 | options[:output_dir] = f 432 | end 433 | options[:port] = Prober::DEFAULT_PORT 434 | opts.on( '-pPORT', '--port=PORT', Integer, "Port for RPC calls (default=#{Prober::DEFAULT_PORT})") do|i| 435 | options[:port] = i 436 | end 437 | 438 | options[:proberoute_dir] = Prober::DEFAULT_PROBEROUTE_DIR 439 | opts.on( '-tPATH', '--tools=PATH', "Probe tool path PATH (default=#{Prober::DEFAULT_PROBEROUTE_DIR})") do|f| 440 | options[:proberoute_dir] = f 441 | end 442 | opts.on( '-v', '--version', 'Version') do 443 | puts VantagePoint::VP_VERSION 444 | puts Prober::PROBER_VERSION 445 | exit 446 | end 447 | end 448 | 449 | # parse! parses ARGV and removes any options found there, as well as params to 450 | # the options 451 | optparse.parse! 452 | 453 | vp=VantagePoint.new(options[:controller_uri],options[:acl], options[:front], options[:port], options[:proberoute_dir],options[:output_dir]) 454 | Signal.trap("INT"){ vp.shutdown(2) } 455 | Signal.trap("TERM"){ vp.shutdown(15) } 456 | Signal.trap("KILL"){ vp.shutdown(9) } 457 | 458 | # # We need the uri of the service to connect a client 459 | $stderr.puts vp.uri 460 | 461 | sleep 5 462 | vp.log("Preparing to check for updates") 463 | update_thread=Thread.new(){ 464 | while true 465 | begin 466 | $stderr.puts 467 | vp.log("Checking for updates") 468 | update_prober=vp.check_for_update(:prober) 469 | restart_prober=update_prober && vp.restart_prober? 470 | restart_vp=vp.check_for_update(:vp) && vp.restart_vp? 471 | restart_config = vp.check_for_update(:vp_config) 472 | restart=restart_vp || restart_prober || restart_config 473 | vp.log("Checked for update. Restart: #{restart}") 474 | if restart 475 | vp.stop_service 476 | vp=VantagePoint.new(options[:controller_uri],options[:acl], options[:front], options[:port], options[:proberoute_dir],options[:output_dir]) 477 | end 478 | rescue 479 | vp.log "Exception: Can't check for update #{$!.to_s}\n" + $!.backtrace.join("\n"), $VP_ERROR 480 | ensure 481 | sleep(36000 + rand(36000)) 482 | end 483 | end 484 | } 485 | 486 | # if we started with a test controller, don't need to check for new URIs 487 | if options[:controller_uri].nil? 488 | register_thread=Thread.new(){ 489 | failed=0 490 | while true 491 | # sleep above the begin so that retry won't hit it automatically 492 | sleep(3600 + rand(3600)) 493 | begin 494 | if failed>0 495 | vp.update_controller(Prober::get_default_controller_uri) 496 | end 497 | vp.register 498 | # if we get here without an exception, set failed=0 499 | failed=0 500 | rescue Exception 501 | failed += 1 502 | if failed==1 503 | vp.log "Exception: Can't update or register #{failed} times, retrying #{$!.class} #{$!.to_s}\n" + $!.backtrace.join("\n"), $VP_ERROR 504 | sleep(120 + (rand 120 )) 505 | sleep(rand(10)) 506 | retry 507 | else 508 | vp.log "Exception: Can't update or register #{failed} times, sleeping #{$!.class} #{$!.to_s}\n" + $!.backtrace.join("\n"), $VP_ERROR 509 | end 510 | end 511 | end 512 | } 513 | end 514 | 515 | begin 516 | $stderr.puts "joining" 517 | update_thread.join 518 | $stderr.puts "thread dead" 519 | rescue 520 | end 521 | vp.shutdown(0) 522 | # DRb might not start properly in certain cases, so want something else to 523 | # wait on, possible. 524 | end #if not $started 525 | --------------------------------------------------------------------------------