|
1 | 1 | require 'spec_helper'
|
2 | 2 | require 'ronin/cli/commands/asn'
|
3 | 3 | require_relative 'man_page_example'
|
| 4 | +require 'ronin/support/network/asn/list' |
| 5 | +require 'ronin/support/cli/printing' |
| 6 | +require 'tempfile' |
4 | 7 |
|
5 | 8 | describe Ronin::CLI::Commands::Asn do
|
6 | 9 | include_examples "man_page"
|
| 10 | + |
| 11 | + subject { described_class.new } |
| 12 | + |
| 13 | + let(:ip2asn_truncated) do |
| 14 | + <<~TSV |
| 15 | + 223.255.233.0\t223.255.233.255\t140694\tAU\tARAFURA-AS-AP Northern Technology Solutions |
| 16 | + 223.255.234.0\t223.255.234.255\t0\tNone\tNot routed |
| 17 | + 223.255.235.0\t223.255.235.255\t140694\tAU\tARAFURA-AS-AP Northern Technology Solutions |
| 18 | + 223.255.236.0\t223.255.239.255\t56000\tCN\tHERBALIFE-CNDC HERBALIFESHANGHAIMANAGEMENT CO.,LTD. |
| 19 | + 223.255.240.0\t223.255.243.255\t55649\tHK\tMETRONET-HK Flat C, 16F, Skyline Tower |
| 20 | + 223.255.244.0\t223.255.247.255\t45117\tIN\tINPL-IN-AP Ishans Network |
| 21 | + 223.255.248.0\t223.255.251.255\t63199\tUS\tCDSC-AS1 |
| 22 | + 223.255.252.0\t223.255.253.255\t58519\tCN\tCHINATELECOM-CTCLOUD Cloud Computing Corporation |
| 23 | + 223.255.254.0\t223.255.254.255\t55415\tSG\tMBS-SG 4 Shenton Way |
| 24 | + ::\t::1\t0\tNone\tNot routed |
| 25 | + 64:ff9b::1:0:0\t100::ffff:ffff:ffff:ffff\t0\tNone\tNot routed |
| 26 | + 100:0:0:1::\t2001:0:ffff:ffff:ffff:ffff:ffff:ffff\t0\tNone\tNot routed |
| 27 | + 2001:1::\t2001:4:111:ffff:ffff:ffff:ffff:ffff\t0\tNone\tNot routed |
| 28 | + 2001:4:112::\t2001:4:112:ffff:ffff:ffff:ffff:ffff\t112\tUS\t-Reserved AS- |
| 29 | + 2001:4:113::\t2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff\t0\tNone\tNot routed |
| 30 | + 2001:200::\t2001:200:5ff:ffff:ffff:ffff:ffff:ffff\t2500\tJP\tWIDE-BB WIDE Project |
| 31 | + 2001:200:600::\t2001:200:6ff:ffff:ffff:ffff:ffff:ffff\t7667\tJP\tKDDLAB KDDI R&D Laboratories, INC. |
| 32 | + 2001:200:700::\t2001:200:8ff:ffff:ffff:ffff:ffff:ffff\t2500\tJP\tWIDE-BB WIDE Project |
| 33 | + 2001:200:900::\t2001:200:9ff:ffff:ffff:ffff:ffff:ffff\t7660\tJP\tAPAN-JP Asia Pacific Advanced Network - Japan |
| 34 | + 2001:200:a00::\t2001:200:dff:ffff:ffff:ffff:ffff:ffff\t2500\tJP\tWIDE-BB WIDE Project |
| 35 | + TSV |
| 36 | + end |
| 37 | + |
| 38 | + let(:ip2asn_file) do |
| 39 | + Tempfile.new.tap do |file| |
| 40 | + file.write(ip2asn_truncated) |
| 41 | + file.rewind |
| 42 | + end |
| 43 | + end |
| 44 | + |
| 45 | + after do |
| 46 | + ip2asn_file.close |
| 47 | + ip2asn_file.unlink |
| 48 | + end |
| 49 | + |
| 50 | + describe '#run?' do |
| 51 | + context "when the file does not exist" do |
| 52 | + before do |
| 53 | + subject.options[:file] = 'nonexistent_file' |
| 54 | + allow(subject).to receive(:download) |
| 55 | + allow(subject).to receive(:print_asn_records) |
| 56 | + end |
| 57 | + |
| 58 | + it 'calls #download' do |
| 59 | + expect(subject).to receive(:download) |
| 60 | + subject.run |
| 61 | + end |
| 62 | + end |
| 63 | + |
| 64 | + context "when options[:update] is true" do |
| 65 | + before do |
| 66 | + subject.options[:update] = true |
| 67 | + allow(subject).to receive(:update) |
| 68 | + allow(subject).to receive(:print_asn_records) |
| 69 | + end |
| 70 | + |
| 71 | + it 'calls #update' do |
| 72 | + expect(subject).to receive(:update) |
| 73 | + subject.run |
| 74 | + end |
| 75 | + end |
| 76 | + |
| 77 | + context "when the file is stale" do |
| 78 | + before do |
| 79 | + allow(Ronin::Support::Network::ASN::List).to receive(:stale?).and_return(true) |
| 80 | + allow(subject).to receive(:update) |
| 81 | + allow(subject).to receive(:print_asn_records) |
| 82 | + end |
| 83 | + |
| 84 | + it 'calls #update' do |
| 85 | + expect(subject).to receive(:update) |
| 86 | + subject.run |
| 87 | + end |
| 88 | + end |
| 89 | + |
| 90 | + context "when given default options and an IP address" do |
| 91 | + before do |
| 92 | + subject.options[:ip] = '223.255.233.1' |
| 93 | + allow(subject).to receive(:query_ip).and_return(true) |
| 94 | + allow(subject).to receive(:print_asn_record) |
| 95 | + end |
| 96 | + |
| 97 | + it "calls #print_asn_record" do |
| 98 | + expect(subject).to receive(:print_asn_record) |
| 99 | + subject.run |
| 100 | + end |
| 101 | + end |
| 102 | + |
| 103 | + # FIXME (cmhobbs) the use of #exit has caused this to be difficult to test |
| 104 | + pending "when given default options and an invalid IP address" |
| 105 | + # context "when given default options and an invalid IP address" do |
| 106 | + # before do |
| 107 | + # subject.options[:ip] = 'invalid' |
| 108 | + # allow(subject).to receive(:query_ip).and_return(false) |
| 109 | + # allow(subject).to receive(:exit) |
| 110 | + # end |
| 111 | + |
| 112 | + # it "calls print_error" do |
| 113 | + # error_message = "could not find a record for the IP: invalid" |
| 114 | + # expect(Ronin::Support::CLI::Printing).to receive(:print_error) |
| 115 | + # #expect { subject.run }.to output(error_message) |
| 116 | + # subject.run |
| 117 | + # end |
| 118 | + # end |
| 119 | + |
| 120 | + context "when given default options and no IP address" do |
| 121 | + before do |
| 122 | + allow(subject).to receive(:update) |
| 123 | + end |
| 124 | + |
| 125 | + it "calls print_asn_records" do |
| 126 | + expect(subject).to receive(:print_asn_records) |
| 127 | + subject.run |
| 128 | + end |
| 129 | + end |
| 130 | + end |
| 131 | + |
| 132 | + describe '#stale?' do |
| 133 | + let(:file) { 'file' } |
| 134 | + |
| 135 | + before do |
| 136 | + subject.options[:file] = file |
| 137 | + end |
| 138 | + |
| 139 | + it 'calls Support::Network::ASN::List.stale? with the file' do |
| 140 | + expect(Ronin::Support::Network::ASN::List).to receive(:stale?).with(file) |
| 141 | + subject.stale? |
| 142 | + end |
| 143 | + end |
| 144 | + |
| 145 | + describe '#download' do |
| 146 | + context "with default parameters" do |
| 147 | + let(:url) { 'https://iptoasn.com/data/ip2asn-combined.tsv.gz' } |
| 148 | + let(:file) { "#{Dir.home}/.cache/ronin/ronin-support/ip2asn-combined.tsv.gz" } |
| 149 | + |
| 150 | + it 'calls Support::Network::ASN::List.download with default parameters' do |
| 151 | + expect(Ronin::Support::Network::ASN::List).to receive(:download).with( |
| 152 | + url: url, |
| 153 | + path: file |
| 154 | + ) |
| 155 | + subject.download |
| 156 | + end |
| 157 | + end |
| 158 | + |
| 159 | + context "with optional parameters" do |
| 160 | + let(:url) { 'http://example.com' } |
| 161 | + let(:file) { 'file' } |
| 162 | + |
| 163 | + before do |
| 164 | + subject.options[:url] = url |
| 165 | + subject.options[:file] = file |
| 166 | + end |
| 167 | + |
| 168 | + it 'calls Support::Network::ASN::List.download with optional parameters' do |
| 169 | + expect(Ronin::Support::Network::ASN::List).to receive(:download).with( |
| 170 | + url: url, |
| 171 | + path: file |
| 172 | + ) |
| 173 | + subject.download |
| 174 | + end |
| 175 | + end |
| 176 | + end |
| 177 | + |
| 178 | + describe "#update" do |
| 179 | + context "with default parameters" do |
| 180 | + let(:url) { 'https://iptoasn.com/data/ip2asn-combined.tsv.gz' } |
| 181 | + let(:file) { "#{Dir.home}/.cache/ronin/ronin-support/ip2asn-combined.tsv.gz" } |
| 182 | + |
| 183 | + it 'calls Support::Network::ASN::List.update with default parameters' do |
| 184 | + expect(Ronin::Support::Network::ASN::List).to receive(:update).with( |
| 185 | + url: url, |
| 186 | + path: file |
| 187 | + ) |
| 188 | + subject.update |
| 189 | + end |
| 190 | + end |
| 191 | + |
| 192 | + context "with optional parameters" do |
| 193 | + let(:url) { 'http://example.com' } |
| 194 | + let(:file) { 'file' } |
| 195 | + |
| 196 | + before do |
| 197 | + subject.options[:url] = url |
| 198 | + subject.options[:file] = file |
| 199 | + end |
| 200 | + |
| 201 | + it 'calls Support::Network::ASN::List.update with optional parameters' do |
| 202 | + expect(Ronin::Support::Network::ASN::List).to receive(:update).with( |
| 203 | + url: url, |
| 204 | + path: file |
| 205 | + ) |
| 206 | + subject.update |
| 207 | + end |
| 208 | + end |
| 209 | + end |
| 210 | + |
| 211 | + describe "#list_file" do |
| 212 | + let(:file) { 'file' } |
| 213 | + |
| 214 | + before do |
| 215 | + subject.options[:file] = file |
| 216 | + end |
| 217 | + |
| 218 | + it 'calls Support::Network::ASN::List.parse' do |
| 219 | + expect(Ronin::Support::Network::ASN::List).to receive(:parse).with(file) |
| 220 | + subject.list_file |
| 221 | + end |
| 222 | + end |
| 223 | + |
| 224 | + describe "#query_ip" do |
| 225 | + let(:ip) { '127.0.0.1' } |
| 226 | + |
| 227 | + it 'calls Support::Network::ASN::List.query_ip' do |
| 228 | + expect(Ronin::Support::Network::ASN).to receive(:query).with(ip) |
| 229 | + subject.query_ip(ip) |
| 230 | + end |
| 231 | + end |
| 232 | + |
| 233 | + describe "#search_asn_records" do |
| 234 | + before do |
| 235 | + subject.options[:file] = ip2asn_file.path |
| 236 | + end |
| 237 | + |
| 238 | + context "given the ipv4 option" do |
| 239 | + it "returns the ipv4 records" do |
| 240 | + subject.options[:ipv4] = true |
| 241 | + records = subject.search_asn_records |
| 242 | + expect(records).to be_a(Ronin::Support::Network::ASN::RecordSet) |
| 243 | + expect(records.to_a.length).to eq(9) |
| 244 | + end |
| 245 | + end |
| 246 | + |
| 247 | + context "given the ipv6 option" do |
| 248 | + it "returns the ipv6 records" do |
| 249 | + subject.options[:ipv6] = true |
| 250 | + records = subject.search_asn_records |
| 251 | + expect(records).to be_a(Ronin::Support::Network::ASN::RecordSet) |
| 252 | + expect(records.to_a.length).to eq(11) |
| 253 | + end |
| 254 | + end |
| 255 | + |
| 256 | + context "given a country code" do |
| 257 | + it "returns the records for the country code" do |
| 258 | + subject.options[:country_code] = 'JP' |
| 259 | + records = subject.search_asn_records |
| 260 | + expect(records).to be_a(Ronin::Support::Network::ASN::RecordSet) |
| 261 | + expect(records.to_a.length).to eq(5) |
| 262 | + end |
| 263 | + end |
| 264 | + |
| 265 | + context "given an ASN number" do |
| 266 | + it "returns the records for the ASN number" do |
| 267 | + subject.options[:number] = 140694 |
| 268 | + records = subject.search_asn_records |
| 269 | + expect(records).to be_a(Ronin::Support::Network::ASN::RecordSet) |
| 270 | + expect(records.to_a.length).to eq(2) |
| 271 | + end |
| 272 | + end |
| 273 | + |
| 274 | + context "given an ASN name" do |
| 275 | + it "returns the records for the ASN name" do |
| 276 | + subject.options[:name] = 'WIDE-BB WIDE Project' |
| 277 | + records = subject.search_asn_records |
| 278 | + expect(records).to be_a(Ronin::Support::Network::ASN::RecordSet) |
| 279 | + expect(records.to_a.length).to eq(3) |
| 280 | + end |
| 281 | + end |
| 282 | + end |
| 283 | + |
| 284 | + describe "#print_asn_records" do |
| 285 | + before do |
| 286 | + subject.options[:file] = ip2asn_file.path |
| 287 | + end |
| 288 | + |
| 289 | + it "calls #print_asn_record for each record" do |
| 290 | + records = subject.search_asn_records |
| 291 | + |
| 292 | + expect(subject).to receive(:print_asn_record).exactly(20).times |
| 293 | + subject.print_asn_records(records) |
| 294 | + end |
| 295 | + end |
| 296 | + |
| 297 | + describe "#print_asn_record" do |
| 298 | + context "with the enum_ips option" do |
| 299 | + before do |
| 300 | + subject.options[:enum_ips] = true |
| 301 | + subject.options[:file] = ip2asn_file.path |
| 302 | + end |
| 303 | + |
| 304 | + it "prints each IP address in the range" do |
| 305 | + record = subject.search_asn_records.first |
| 306 | + |
| 307 | + # NOTE: we're not testing output here due to the large number of IPs |
| 308 | + expect(record.range).to receive(:each) |
| 309 | + subject.print_asn_record(record) |
| 310 | + end |
| 311 | + end |
| 312 | + |
| 313 | + context "with the verbose option set" do |
| 314 | + before do |
| 315 | + subject.options[:verbose] = true |
| 316 | + subject.options[:file] = ip2asn_file.path |
| 317 | + end |
| 318 | + |
| 319 | + it "prints the record in verbose format" do |
| 320 | + record = subject.search_asn_records.first |
| 321 | + |
| 322 | + output( |
| 323 | + "[ AS140694 ]\n\n" \ |
| 324 | + "ASN: 140694\n" \ |
| 325 | + "IP range: 223.255.233.0 - 223.255.233.255\n" \ |
| 326 | + "Country: AU\n" \ |
| 327 | + "Name: ARAFURA-AS-AP Northern Technology Solutions\n\n" |
| 328 | + ).to_stdout |
| 329 | + end |
| 330 | + end |
| 331 | + |
| 332 | + context "with default options" do |
| 333 | + before do |
| 334 | + subject.options[:file] = ip2asn_file.path |
| 335 | + end |
| 336 | + |
| 337 | + it "prints the record in default format" do |
| 338 | + record = subject.search_asn_records.first |
| 339 | + |
| 340 | + expect { subject.print_asn_record(record) }.to output( |
| 341 | + "223.255.233.0 - 223.255.233.255 AS140694\ (AU) ARAFURA-AS-AP Northern Technology Solutions\n" |
| 342 | + ).to_stdout |
| 343 | + end |
| 344 | + end |
| 345 | + end |
7 | 346 | end
|
0 commit comments