Class: Scriptroute::Ally
- Inherits:
-
Object
- Object
- Scriptroute::Ally
- Defined in:
- lib/scriptroute/ally.rb
Constant Summary collapse
- @@result_cache =
the cache of previously seen results is a class variable.
Hash.new
Class Method Summary collapse
-
.aliases?(ip_a, ip_b) ⇒ Boolean
Checks the cache, and if no entry is present, creates a new alias resolution test object to probe.
-
.before(seq1, seq2) ⇒ Boolean
a helper function to handle comparing ipid’s, as they are unsigned short counters that can wrap.
-
.each_alias {|String, String| ... } ⇒ Object
Iterate through the result cache, yielding each pair of IP addresses found to be aliases.
-
.make_result_cache_persistent(dbfilename) ⇒ void
use bdb41 to save the cache of results persistently, in case we would like to restore it.
-
.to_key(ip_a, ip_b) ⇒ String
keys in the cache are concatenations of IP addresses.
Instance Method Summary collapse
-
#assert_response_non_bogus(resp) ⇒ Object
responses we can get include address unreachable, which means we’re not talking to the intended router.
-
#before(seq1, seq2) ⇒ Boolean
a helper function to handle comparing ipid’s, as they are unsigned short counters that can wrap.
-
#initialize(ip_a, ip_b, type = 'udp') ⇒ Ally
constructor
create an object that will represent our attempt to test two addresses.
-
#ip_to_name(ip) ⇒ String
do a reverse lookup on an IP address.
-
#is? ⇒ Boolean
Whether the “verdict” is “ALIAS”, ignoring the explanation.
- #is_alias(msg) ⇒ void
-
#my_key ⇒ String
a quick shorthand for finding where our object belongs in the cache.
- #not_alias(msg) ⇒ void
-
#to_s ⇒ String
The “verdict”.
-
#try_undns(msg) ⇒ Object
if we can’t tell using packets, try using undns to guess using the names attached to these interfaces.
- #unknown(msg) ⇒ void
Constructor Details
#initialize(ip_a, ip_b, type = 'udp') ⇒ Ally
create an object that will represent our attempt to test two addresses. the parameters may be hostnames instead of addresses.
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 |
# File 'lib/scriptroute/ally.rb', line 282 def initialize(ip_a, ip_b, type='udp') @a = packet_creator(ip_a, type) @b = packet_creator(ip_b, type) # for now, we don't know. @verdict = "UNKNOWN: Failed to complete" # we'll throw :resolved when we've figured it out; this is # to simplify the if/elsif/elsif insanity catch :resolved do # the quick test; handling this test through the packet # test causes confusion. It is after packet construction # so that the names are looked up to addresses if( @a.ip_dst == @b.ip_dst ) then is_alias "trivial, #{@a.ip_dst} = #{@b.ip_dst}" end ## this is entirely too complicated, and needs a rewrite. packets = Scriptroute::send_train([ Struct::DelayedPacket.new(0,@a), Struct::DelayedPacket.new(0.001,@b) ]) ## try again if we had a pcap overload style problem if(packets.length < 2 || packets[0] == nil || packets[0].probe == nil || packets[1] == nil || packets[1].probe == nil) then packets = Scriptroute::send_train([ Struct::DelayedPacket.new(0,@a), Struct::DelayedPacket.new(0.001,@b) ]) if(packets.length < 2 || packets[0] == nil || packets[0].probe == nil || packets[1] == nil || packets[1].probe == nil) then try_undns "Internal error: #{@a.ip_dst} and #{@b.ip_dst}" end end if(packets[0].response && packets[1].response) then assert_response_non_bogus(packets[0]) assert_response_non_bogus(packets[1]) id0 = packets[0].response.packet.ip_id id1 = packets[1].response.packet.ip_id if(packets[0].response.packet.ip_src == packets[1].response.packet.ip_src) then is_alias "mercator/source address: #{merc(packets[0])} #{merc(packets[1])}" elsif( id0 == id1 ) then # when they're the same, it's either: if ( id0 == 0 ) then # a) a lack of implementation try_undns "IPIDs not used: both are zero" else # b) not aliases. not_alias "Same IPID." end elsif(before(id0-10, id1) && before(id1, id0+200)) then # adding a delay here (the 0.40) seems to increase the likelihood # of a response. packetz = Scriptroute::send_train([ Struct::DelayedPacket.new(0.40,@b), Struct::DelayedPacket.new(0.001,@a) ]) if(packetz[0] == nil || packetz[1] == nil) then packetz = Scriptroute::send_train([ Struct::DelayedPacket.new(0.40,@b), Struct::DelayedPacket.new(0.001,@a) ]) if(packetz[0] == nil || packetz[1] == nil) then raise "couldn't send the second set of packets" end end if(packetz[0].response && packetz[1].response) then id2 = packetz[0].response.packet.ip_id id3 = packetz[1].response.packet.ip_id assert_response_non_bogus(packetz[0]) assert_response_non_bogus(packetz[1]) if(before(id2-10, id3) && before(id3, id2+200) && before(id0, id2) && before(id1, id3) && unique_ids( [ id0, id1, id2, id3 ] ) ) then is_alias "ally/ipid: #{[id0, id1, id2, id3].join(', ')}" else not_alias "disparate ids: #{[id0, id1, id2, id3].join(', ')}" end elsif(packetz[0].response || packetz[1].response) then last = packetz[ (packetz[0].response) ? 0 : 1 ] assert_response_non_bogus(last) id2 = last.response.packet.ip_id if(before(id0, id2) && before(id1, id2) && unique_ids( [ id0, id1, id2 ] )) then is_alias "ally/ipid; less response: #{[id0, id1, id2].join(', ')}" else not_alias "disparate ids (3): #{[id0, id1, id2].join(', ')}" end else is_alias "ally/ipid; presumptive (second round had no responses): #{[id0, id1].join(', ')}" end else not_alias "quick (2): #{[id0, id1].join(', ')}" ## #{[id0, id1].map {|v| (((v&0xff)*256) + v/256)}.join(', ')} " end elsif(packets[0].response || packets[1].response) then first = packets[ (packets[0].response) ? 0 : 1 ] # we received only one response. # try sending again, reordered. packetz = Scriptroute::send_train([ Struct::DelayedPacket.new(0,@b), Struct::DelayedPacket.new(0.001,@a) ]) if(packetz[0].response || packetz[1].response) then # we received at least one response second = packetz[ (packetz[0].response) ? 0 : 1 ] assert_response_non_bogus(second) if((second.probe.packet.ip_dst != second.response.packet.ip_src || first.probe.packet.ip_dst != first.response.packet.ip_src) && first.response.packet.ip_src == second.response.packet.ip_src) then # shows the signature of a cisco ( responds with a different source address ) if(second.probe.packet.ip_dst != first.probe.packet.ip_dst) then # and responses to two different requests # puts second.response.packet # puts first.response.packet is_alias "mercator/source address rate limited: #{merc(first)} #{merc(second)}" else # the destination of both probes we got answers to was the same. # the other destination was unresponsive. unresponsive(packets[packets[0].response ? 1 : 0].probe.packet.ip_dst) end else # not necessarily true? might have just lost the first packet in the # first round. unresponsive(packets[packets[0].response ? 1 : 0].probe.packet.ip_dst) end else unresponsive(packets[packets[0].response ? 1 : 0].probe.packet.ip_dst) end else unresponsive(@a.ip_dst, @b.ip_dst) end fail "shouldn't get here under any circumstances." end # catch. end |
Class Method Details
.aliases?(ip_a, ip_b) ⇒ Boolean
Checks the cache, and if no entry is present, creates a new alias resolution test object to probe.
440 441 442 443 444 445 446 |
# File 'lib/scriptroute/ally.rb', line 440 def Ally.aliases?(ip_a, ip_b) key = Ally.to_key(ip_a, ip_b) if(!@@result_cache.has_key?(key)) then Ally.new(ip_a, ip_b) end @@result_cache[key] =~ /^ALIAS/ end |
.before(seq1, seq2) ⇒ Boolean
a helper function to handle comparing ipid’s, as they are unsigned short counters that can wrap.
100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/scriptroute/ally.rb', line 100 def Ally.before(seq1,seq2) diff = seq1-seq2 # emulate signed short arithmetic. if (diff > 32767) then diff-=65535; elsif (diff < -32768) then diff+=65535; end # puts "#{seq1} #{diff < 0 ? "" : "not"} before #{seq2}\n" (diff < 0) end |
.each_alias {|String, String| ... } ⇒ Object
Iterate through the result cache, yielding each pair of IP addresses found to be aliases.
47 48 49 50 51 52 53 |
# File 'lib/scriptroute/ally.rb', line 47 def Ally.each_alias @@result_cache.each { |k,v| if v =~ /^ALIAS/ then yield k.split(":") end } end |
.make_result_cache_persistent(dbfilename) ⇒ void
the cache does not expire entries. The cache should probably not be used after, say, a month, since topologies can change.
if bdb41 cannot be loaded, the cache will not be made persistent, and an error will print to stderr.
This method returns an undefined value.
use bdb41 to save the cache of results persistently, in case we would like to restore it. This sets the cache to be on disk (so we can read from it, and it will exist after the script) instead of in memory (created and destroyed with every invocation).
36 37 38 39 40 41 42 43 |
# File 'lib/scriptroute/ally.rb', line 36 def Ally.make_result_cache_persistent(dbfilename) begin require "bdb41" @@result_cache = BDB::Hash.new(dbfilename, nil, BDB::CREATE) rescue LoadError $stderr.puts "Unable to make result cache persistent: install bdb41 (libdb4.1-ruby)" end end |
.to_key(ip_a, ip_b) ⇒ String
keys in the cache are concatenations of IP addresses. since alias relations are symmetric (reflexive?), the addresses are sorted first.
59 60 61 |
# File 'lib/scriptroute/ally.rb', line 59 def Ally.to_key(ip_a, ip_b) [ ip_a, ip_b ].sort.join(':') end |
Instance Method Details
#assert_response_non_bogus(resp) ⇒ Object
responses we can get include address unreachable, which means we’re not talking to the intended router. Check first.
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 |
# File 'lib/scriptroute/ally.rb', line 199 def assert_response_non_bogus(resp) if( ! resp.response ) then raise "(bug!) assert_response_non_bogus shouldn't check that a response was received" end if(@a.is_a?(Scriptroute::UDP)) then # if we tried to probe using udp traceroute-like, we're expecting a port unreach. # if we don't get it, likely filtered, try undns if((! resp.response.packet.is_a?(Scriptroute::ICMP)) or ( resp.response.packet.icmp_type != Scriptroute::ICMP::ICMP_UNREACH ) or ( resp.response.packet.icmp_code != Scriptroute::ICMP::ICMP_UNREACH_PORT )) then try_undns "filtered: #{resp.probe.packet.ip_dst}" end elsif(@a.is_a?(Scriptroute::TCP)) then # if we tried to probe using tcp 80, we're expecting a tcp rst. # if we don't get it, likely filtered, try undns if(! resp.response.packet.is_a?(Scriptroute::TCP)) then try_undns "filtered: #{resp.probe.packet.ip_dst}" end elsif(@a.is_a?(Scriptroute::ICMP) && @a.icmp_type == Scriptroute::ICMP_ECHO ) then # if we tried to probe using tcp 80, we're expecting a tcp rst. # if we don't get it, likely filtered, try undns if((! resp.response.packet.is_a?(Scriptroute::ICMP)) or resp.response.packet.icmp_type != Scriptroute::ICMP_ECHOREPLY) then try_undns "filtered: #{resp.probe.packet.ip_dst}" end else # we don't seem to know what to do. fail "Can't recognize expected response to #{@a.to_s}." end end |
#before(seq1, seq2) ⇒ Boolean
a helper function to handle comparing ipid’s, as they are unsigned short counters that can wrap.
114 115 116 |
# File 'lib/scriptroute/ally.rb', line 114 def before(seq1,seq2) Ally.before(seq1,seq2) end |
#ip_to_name(ip) ⇒ String
do a reverse lookup on an IP address. Be prepared to handle an exception, as in safe mode, this is not permitted.
123 124 125 126 127 128 129 130 |
# File 'lib/scriptroute/ally.rb', line 123 def ip_to_name(ip) begin (Socket.gethostbyname(ip)[0]).gsub(/\"/,'') rescue => e # provide a more informative exception. raise "#{ip} #{e}" end end |
#is? ⇒ Boolean
Returns whether the “verdict” is “ALIAS”, ignoring the explanation.
430 431 432 433 434 |
# File 'lib/scriptroute/ally.rb', line 430 def is? # redundancy is for testing. want to be able to compare # true == true. true & (@verdict =~ /^ALIAS/) end |
#is_alias(msg) ⇒ void
This method returns an undefined value.
84 85 86 87 88 |
# File 'lib/scriptroute/ally.rb', line 84 def is_alias(msg) @verdict = "ALIAS! #{msg}" @@result_cache[my_key] = @verdict; throw :resolved end |
#my_key ⇒ String
a quick shorthand for finding where our object belongs in the cache.
66 67 68 |
# File 'lib/scriptroute/ally.rb', line 66 def my_key Ally.to_key(@a.ip_dst, @b.ip_dst) end |
#not_alias(msg) ⇒ void
This method returns an undefined value.
91 92 93 94 95 |
# File 'lib/scriptroute/ally.rb', line 91 def not_alias(msg) @verdict = "NOT ALIAS. #{msg}" @@result_cache[my_key] = @verdict; throw :resolved end |
#to_s ⇒ String
Returns the “verdict”.
425 426 427 |
# File 'lib/scriptroute/ally.rb', line 425 def to_s @verdict end |
#try_undns(msg) ⇒ Object
if we can’t tell using packets, try using undns to guess using the names attached to these interfaces.
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/scriptroute/ally.rb', line 134 def try_undns(msg) # we can't tell using packets whether the two are aliases. if(!$have_undns) then # throws out (return implied) unknown "#{msg}; undns not loaded." end # try a reverse lookup, then match the names using established # rules. This requires access to the file system so won't # work if run remotely. (it will jump to the rescue line and # print unknown). begin # the next fragment is designed to try both lookups first, # then try both through undns second. this means we won't # complain about undns unless we would have had a chance of # using it. begin a_name, b_name = [ @a.ip_dst, @b.ip_dst ].map { |dst| ip_to_name(dst) } begin a_uniq, b_uniq = [ a_name, b_name ].map { |name| uniq = Undns.get_identifier(0, name) if( uniq == nil || uniq == '') then raise "#{name} lacks unique fragments" end uniq } if(a_uniq == b_uniq) then is_alias "name: #{a_uniq}, otherwise #{msg}" else not_alias "name: #{a_uniq} != #{b_uniq} and #{msg}" end rescue => e # rule them out if the names say different cities, even # if we can't tell the specific pattern to prove that # addresses are aliases. this is just an optimization -- # unknown is usually treated as "no" anyway. a_city, b_city = [ a_name, b_name ].map { |name| city = Undns.get_loc(0, name) if( city == nil || city == '') then raise "#{name} lacks unique fragments and city location" end city } if(a_city == b_city) then unknown "#{msg}; undns failed and cities are the same: #{e}" else not_alias "name: cities #{a_city} != #{b_city} and #{msg}" end end end rescue SecurityError => e unknown "#{msg}; undns failed (securityerror): #{e}" rescue LoadError => e unknown "#{msg}; loaderror undns failed: #{e}" rescue => e # unable to lookup, don't have undns, etc. unknown "#{msg}; undns failed: #{e}" end end |
#unknown(msg) ⇒ void
This method returns an undefined value.
77 78 79 80 81 |
# File 'lib/scriptroute/ally.rb', line 77 def unknown(msg) @verdict = "UNKNOWN. #{msg}" @@result_cache[my_key] = @verdict; throw :resolved # much like a return in the calling scope. end |