diff --git a/README.md b/README.md index eea173a..37d942a 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ Requires Bash v4.2+. Tested on: * *Autonomous system number lookup with AS ranking, operational region, BGP stats, peering and prefix informations* -![asnlookup](https://github.com/nitefood/asn/assets/24555810/4507085a-facf-4383-a9d4-573161454bec) +![asnlookup](https://github.com/nitefood/asn/assets/24555810/758890d8-7103-41f3-978e-ba5799213af6) * *Hostname/URL lookup* @@ -252,13 +252,21 @@ To run the script without installing it locally, you have the following options: ## Installation -### Prerequisite packages - This script requires **BASH v4.2** or later. You can check your version by running from your shell: -`bash -c 'echo $BASH_VERSION'` +``` +bash -c 'echo $BASH_VERSION' +``` + +After installation, you can use the script by running the `asn` command. + +### Method 1: Install prerequisites + manual download + +> *Note: this method is **recommended** as it will always get you the **latest version** of the script.* + +
STEP 1. Install prerequisite packages

-Some additional packages are also required for full functionality: +Some packages are required for full functionality: * **Debian 10 / Ubuntu 20.04 (or newer):** @@ -304,28 +312,61 @@ Some additional packages are also required for full functionality: dnf -y install curl whois bind-utils mtr jq nmap nmap-ncat ipcalc aha grepcidr ``` -* **Manjaro/Arch Linux:** *(thanks [Worty](https://github.com/worty))* +* **openSUSE Leap 15.5 (or newer), openSUSE Tumbleweed** ``` - yay -S asn-git + zypper in -y curl whois bind-utils mtr jq nmap ncat ipcalc aha grepcidr ``` -* **Alpine Linux 3.18 (or newer)** *(thanks [Francesco Colista](https://github.com/fcolista))* +* **FreeBSD**: ``` - apk add -X https://dl-cdn.alpinelinux.org/alpine/v3.19/community asn + env ASSUME_ALWAYS_YES=YES pkg install bash coreutils curl whois mtr jq ipcalc grepcidr nmap aha ``` -* **openSUSE Leap 15.5 (or newer), openSUSE Tumbleweed** +* **Windows**: + + * **using [WSL2](https://docs.microsoft.com/en-us/windows/wsl/about) (recommended):** + Install Windows Subsystem for Linux (v2) by following Microsoft's [guide](https://docs.microsoft.com/en-us/windows/wsl/install-win10#manual-installation-steps). On step 6, choose one of the Linux distributions listed above (Ubuntu 20.04 LTS is recommended). + Once your WSL2 system is up and running, open a Linux terminal and follow the prerequisite installation instructions above for your distribution of choice. + + > *Note for Windows users: Check [this](https://devblogs.microsoft.com/commandline/systemd-support-is-now-available-in-wsl/) page for details on how to activate **systemd** if you plan to install the [asn service](#installing-the-asn-server-as-a-system-service).* + + * **using [Cygwin](https://cygwin.com/index.html):** + Most of the prerequisite packages listed above for *Debian 10 / Ubuntu 20.04 (or newer)* are obtainable directly with Cygwin's own Setup wizard (or through scripts like *apt-cyg*). You will still have to manually compile (or find a suitable third-party precompiled binary) the *mtr*, *grepcidr* and *aha* tools. Instructions on how to do so can be found directly on the respective projects homepages. +

+ +
STEP 2. Script download and installation

+ +Afterwards, to install the **asn** script from your shell to **/usr/bin**: + +`curl "https://raw.githubusercontent.com/nitefood/asn/master/asn" > /usr/bin/asn && chmod 0755 /usr/bin/asn` + +

+ +### Method 2: Installing a packaged version of the script + +> *Note: packages may not reflect the latest version, check [Repology](https://repology.org/project/asn/versions) first.* + +Packaged versions of the tool are available for the following distributions: + +
Distribution list

+ +* **Debian Sid / Ubuntu 24.04 (or newer):** *(thanks [Marcos Rodrigues de Carvalo](https://github.com/odaydebian))* ``` - zypper in -y curl whois bind-utils mtr jq nmap ncat ipcalc aha grepcidr + sudo apt update && sudo apt install asn ``` +* **Manjaro / Arch Linux:** *(thanks [Worty](https://github.com/worty))* -* **FreeBSD**: + ``` + yay -S asn-git + ``` + +* **Alpine Linux 3.18 (or newer)** *(thanks [Francesco Colista](https://github.com/fcolista))* ``` - env ASSUME_ALWAYS_YES=YES pkg install bash coreutils curl whois mtr jq ipcalc grepcidr nmap aha + apk add -X https://dl-cdn.alpinelinux.org/alpine/v3.19/community asn ``` * **NixOS** *(thanks [devhell](https://github.com/devhell))* @@ -338,32 +379,15 @@ Some additional packages are also required for full functionality: brew install asn ``` - *Notes for MacOS users:* - - * *If `mtr` still can't be found after running the command above, [this](https://docs.brew.sh/FAQ#my-mac-apps-dont-find-usrlocalbin-utilities) may help to fix it.* - * *Homebrew has a [policy](https://github.com/Homebrew/homebrew-core/issues/35085#issuecomment-447184214) not to install any binary with the **setuid** bit, and mtr (or actually, the mtr-packet helper binary that comes with it) requires to elevate to root to perform traces (good explanations for this can be found [here](https://github.com/traviscross/mtr/issues/204#issuecomment-723961118) and [here](https://github.com/traviscross/mtr/blob/master/SECURITY)). If mtr (and therefore `asn`) traces are not working on your system, you should either run `asn` as root using **sudo**, or set the proper SUID permission bit on the mtr (or better, on the mtr-packet) binary.* - -* **Windows**: - - * **using [WSL2](https://docs.microsoft.com/en-us/windows/wsl/about) (recommended):** - Install Windows Subsystem for Linux (v2) by following Microsoft's [guide](https://docs.microsoft.com/en-us/windows/wsl/install-win10#manual-installation-steps). On step 6, choose one of the Linux distributions listed above (Ubuntu 20.04 LTS is recommended). - Once your WSL2 system is up and running, open a Linux terminal and follow the prerequisite installation instructions above for your distribution of choice. - *Note for WSL2 users:* - * ~~*systemd is not currently available in WSL2, so you won't be able to run the **asn server** in daemon mode as described below (if you want server mode you'll have to launch it manually using `asn -l`). An alternative could be to run it as a background process (optionally also using `nohup`), or using Windows' own task scheduler to start it at boot.*~~ **UPDATE: systemd is now supported on WSL2. Check [this](https://devblogs.microsoft.com/commandline/systemd-support-is-now-available-in-wsl/) page for details on how to activate it.** - * **using [Cygwin](https://cygwin.com/index.html):** - Most of the prerequisite packages listed above for *Debian 10 / Ubuntu 20.04 (or newer)* are obtainable directly with Cygwin's own Setup wizard (or through scripts like *apt-cyg*). You will still have to manually compile (or find a suitable third-party precompiled binary) the *mtr*, *grepcidr* and *aha* tools. Instructions on how to do so can be found directly on the respective projects homepages. - -### Script download and installation + >*Note for MacOS users:* + > + > *Homebrew has a [policy](https://github.com/Homebrew/homebrew-core/issues/35085#issuecomment-447184214) not to install any binary with the **setuid** bit, and mtr (or actually, the mtr-packet helper binary that comes with it) requires to elevate to root to perform traces (good explanations for this can be found [here](https://github.com/traviscross/mtr/issues/204#issuecomment-723961118) and [here](https://github.com/traviscross/mtr/blob/master/SECURITY)). If mtr (and therefore `asn`) traces are not working on your system, you should either run `asn` as root using **sudo**, or set the proper SUID permission bit on the mtr (or better, on the mtr-packet) binary.* -Afterwards, to install the **asn** script from your shell to **/usr/bin**: - -`curl "https://raw.githubusercontent.com/nitefood/asn/master/asn" > /usr/bin/asn && chmod 0755 /usr/bin/asn` - -You can then use the script by running `asn`. +

-### Installing the *asn server* as a system service +### *(Optional)* Installing the *asn server* as a system service -*Note: this step is optional, and these instructions are only for systemd-based Linux systems (most current major distributions).* +>*Note: this step is optional, and these instructions are only for **systemd**-based Linux systems (most current major distributions).* To control the **asn server** with utilities like *systemctl* and *service*, and to enable it to automatically start at boot, follow these steps: @@ -447,20 +471,20 @@ where `TARGET` can be one of the following: * enables lookup and path tracing for targets **(this is the default behavior)** - *.asnrc option equivalent: `MTR_TRACING=true` (default: `true`)* + >*.asnrc option equivalent: `MTR_TRACING=true` (default: `true`)* * `[-d]` * enables detailed trace mode (more info below) - *.asnrc option equivalent: `DETAILED_TRACE=true` (default: `false`)* + >*.asnrc option equivalent: `DETAILED_TRACE=true` (default: `false`)* * `[-n]` * disables path tracing and only outputs lookup info _(for IP targets)_ * disables additional INETNUM/origin lookups _(for AS targets)_ - *.asnrc option equivalent: `MTR_TRACING=false` (default: `true`), `ADDITIONAL_INETNUM_LOOKUP=false` (default: `true`)* + >*.asnrc option equivalent: `MTR_TRACING=false` (default: `true`), `ADDITIONAL_INETNUM_LOOKUP=false` (default: `true`)* * `[-s]` @@ -495,25 +519,25 @@ where `TARGET` can be one of the following: * enables compact JSON output. Useful for feeding the output into other tools (like `jq` or other parsers), or storing the lookup results. - *.asnrc option equivalent: `JSON_OUTPUT=true` (default: `false`)* + >*.asnrc option equivalent: `JSON_OUTPUT=true` (default: `false`)* * `-J` * enables pretty-printed JSON output. - *.asnrc option equivalent: `JSON_PRETTY=true` (default: `false`)* + >*.asnrc option equivalent: `JSON_PRETTY=true` (default: `false`)* * `-m` * enables monochrome mode (disables all colors). - *.asnrc option equivalent: `MONOCHROME_MODE=true` (default: `false`)* + >*.asnrc option equivalent: `MONOCHROME_MODE=true` (default: `false`)* * `-v` * Enable debug messages (will display all URLs being queried to help identify external API slowdowns) - *.asnrc option equivalent: `ASN_DEBUG=true` (default: `false`)* + >*.asnrc option equivalent: `ASN_DEBUG=true` (default: `false`)* * `-h` @@ -525,13 +549,13 @@ where `TARGET` can be one of the following: * IP address (v4/v6) to bind the listening server to (e.g. `asn -l 0.0.0.0`) - *.asnrc option equivalent: `DEFAULT_SERVER_BINDADDR_v4=""` (default: `"127.0.0.1"`) and `DEFAULT_SERVER_BINDADDR_v6=""` (default: `"::1"`)* + >*.asnrc option equivalent: `DEFAULT_SERVER_BINDADDR_v4=""` (default: `"127.0.0.1"`) and `DEFAULT_SERVER_BINDADDR_v6=""` (default: `"::1"`)* * `BIND_PORT` * TCP Port to bind the listening server to (e.g. `asn -l 12345`) - *.asnrc option equivalent: `DEFAULT_SERVER_BINDPORT=""` (default: `"49200"`)* + >*.asnrc option equivalent: `DEFAULT_SERVER_BINDPORT=""` (default: `"49200"`)* * `BIND_ADDRESS BIND_PORT` @@ -541,7 +565,7 @@ where `TARGET` can be one of the following: * Enable verbose output and debug messages in server mode - *.asnrc option equivalent: `ASN_DEBUG=true` (default: `false`)* + >*.asnrc option equivalent: `ASN_DEBUG=true` (default: `false`)* * `--allow host[,host,...]` @@ -811,7 +835,7 @@ Target types can be mixed and queried in a single run. Targets can be piped to t Shodan scan results can be output in JSON mode by passing the `-j` or `-J` options. -*Note: the Nmap tool is needed to use this feature, but note that **no packets whatsoever** are sent to the targets. Nmap is only required to break down CIDR blocks into single IPs (as a calculator tool).* +>*Note: the Nmap tool is needed to use this feature, but note that **no packets whatsoever** are sent to the targets. Nmap is only required to break down CIDR blocks into single IPs (as a calculator tool).* ## Mapping the IP(v4/v6) address space of specific countries @@ -856,10 +880,15 @@ last | asn -g The tool can be instructed to output lookup results in JSON mode by using the `-j` (compact JSON) or `-J` (pretty-printed JSON) command line options: -*Example 1 - IPv4 lookup:* +
Example 1 - IPv4 lookup

+ +##### Command: + +`asn -J 8.8.8.8` + +##### Output: -```jsonp -root@KRUSTY:~# asn -J 8.8.8.8 +```json { "target": "8.8.8.8", "target_type": "ipv4", @@ -920,89 +949,97 @@ root@KRUSTY:~# asn -J 8.8.8.8 ] } ``` +

+
Example 2 - ASN lookup

-*Example 2 - ASN lookup:* +##### Command: -```jsonp -root@KRUSTY:~# asn -J 5505 +`asn -J 5505` + +##### Output: + +```json { "target": "5505", "target_type": "asn", "result": "ok", "reason": "success", - "version": "0.72.1", - "request_time": "2022-03-28T21:59:51", - "request_duration": 4, + "version": "0.76.0", + "request_time": "2024-02-22T00:11:41", + "request_duration": 10, "result_count": 1, "results": [ { "asn": "5505", "asname": "VADAVO, ES", - "org": "VDV-VLC-RED05 VDV-VLC-RED05 - CLIENTES DATACENTER", + "asrank": 3779, + "org": "VDV-VLC-RED06 VDV-VLC-RED06 - CLIENTES TELECOM", "holder": "VADAVO SOLUCIONES SL", "abuse_contacts": [ "abuse@vadavo.com" ], "registration_date": "2016-12-13T08:28:07", "ixp_presence": [ - "NIXVAL-ix: Peering LAN1", "DE-CIX Madrid: DE-CIX Madrid Peering LAN", - "ESPANIX Madrid Lower LAN", - "IXPlay Global Peers" + "ESPANIX Madrid Lower LAN" ], "prefix_count_v4": 8, "prefix_count_v6": 1, - "bgp_peer_count": 32, + "bgp_peer_count": 36, "bgp_peers": { "upstream": [ "1299", "6939", - "3262", - "34549", - "13030", - "25369", - "33891", - "35280", + "59432", + "174", "25091", + "33891", + "8218", "41327", - "1239", - "34927", - "60501", + "48348", + "35280", + "35625", "4455", - "24482", - "13786", - "8218", + "13030", + "202766", + "3303", + "6057", + "137409", "15830" ], "downstream": [ - "200509", "48952", - "207495", "208248", - "205093", - "202054", - "205086" + "205086", + "202054" ], "uncertain": [ - "61573", - "51185", - "271253", - "264479", - "34854", + "47787", + "39384", + "37721", + "36236", "25160", - "37721" + "24482", + "51185", + "49544", + "41047", + "29680", + "29049", + "212483", + "14840", + "34927" ] }, "announced_prefixes": { "v4": [ - "185.123.206.0/24", - "185.210.227.0/24", - "185.123.205.0/24", "185.123.204.0/24", "185.123.207.0/24", - "185.210.226.0/24", "188.130.247.0/24", - "185.210.225.0/24" + "185.210.226.0/24", + "185.210.227.0/24", + "185.123.205.0/24", + "185.210.225.0/24", + "185.123.206.0/24" ], "v6": [ "2a03:9320::/32" @@ -1021,7 +1058,14 @@ root@KRUSTY:~# asn -J 5505 ] }, "inetnums_announced_by_other_as": { - "v4": [], + "v4": [ + { + "prefix": "188.130.254.0/24", + "origin_asn": "", + "origin_org": "", + "is_announced": false + } + ], "v6": [] } } @@ -1029,52 +1073,88 @@ root@KRUSTY:~# asn -J 5505 } ``` -*Example 3 - enumerating abuse contacts for every IP to which a hostname resolves:* +

+
Example 3 - enumerating abuse contacts for every IP to which a hostname resolves

-```shell -root@KRUSTY:~# asn -j www.google.com | jq '[.results[].abuse_contacts[]] | unique[]' +##### Command: + +`asn -j www.google.com | jq '[.results[].abuse_contacts[]] | unique[]'` + +##### Output: + +``` "network-abuse@google.com" "ripe-contact@google.com" ``` -*Example 4 - enumerating known vulnerabilities for a target:* +

+
Example 4 - enumerating known vulnerabilities for a target

-```shell -root@KRUSTY:~# asn -j 45.67.34.100 | jq '.results[].fingerprinting.vulns[]' +##### Command: + +`asn -j 45.67.34.100 | jq '.results[].fingerprinting.vulns[]'` + +##### Output: +``` "CVE-2017-15906" "CVE-2018-15919" ``` -*Example 5 - upstream/transit AS lookup for a given IP:* +

+
Example 5 - upstream/transit AS lookup for a given IP

-```shell -root@KRUSTY:~#: asn -Ju 72.17.119.201 +##### Command: + +`asn -Ju 72.17.119.201` + +##### Output: + +```json { "target": "72.17.119.201", "target_type": "ipv4", "result": "ok", "reason": "success", - "version": "0.74", - "request_time": "2023-05-11T23:46:12", - "request_duration": 1, + "version": "0.76.0", + "request_time": "2024-02-22T00:15:25", + "request_duration": 3, "result_count": 1, "results": [ { + "prefix": "72.17.0.0/17", "origin_as": "33363", "origin_as_name": "BHN-33363, US", + "origin_as_rank": 435, + "upstreams_count": 1, "upstreams": [ { "asn": "7843", "asname": "TWC-7843-BB, US", - "probability": 100 + "probability": 100, + "is_tier1": false } ], - "multiple_transits": false + "multiple_upstreams": false } ] } ``` +

+
Example 6 - enumerating unannounced address blocks for a given AS

+ +##### Command: + +`asn -j AS5505 | jq -r '.results[].inetnums_announced_by_other_as.v4[] | select(.is_announced==false) | .prefix'` + +##### Output: + +``` +188.130.254.0/24 +``` + +

+ #### Remotely (API endpoint) By running the script in [server mode](#running-lookups-from-the-browser), it is possible to use it as a self-hosted lookup API service by running HTTP queries against it and retrieving the results in compact or pretty-printed JSON format. The server exposes the `asn_lookup_json` and `asn_lookup_jsonp` endpoints for this purpose. The syntax is the same as with normal browser-based remote queries. @@ -1125,6 +1205,8 @@ An initial version of this script was featured in the **Security Trails** blog p Thanks [Massimo Candela](https://github.com/massimocandela/) for your support and excellent work on [IPmap](https://ipmap.ripe.net/), [BGPlay](https://github.com/massimocandela/BGPlay) and [TraceMON](https://github.com/RIPE-NCC/tracemon)! +Thanks to all the awesome contributors for their code, ideas, suggestions, packages and bug reports! + ## Feedback and contributing Any feedback or pull request to improve the code is welcome. Feel free to contribute! diff --git a/asn b/asn index afd114c..a46d4f3 100755 --- a/asn +++ b/asn @@ -12,7 +12,7 @@ # │ (Launch the script without parameters or visit the project's homepage for usage info)│ # ╰──────────────────────────────────────────────────────────────────────────────────────╯ -ASN_VERSION="0.75.3" +ASN_VERSION="0.76.0" # ╭──────────────────╮ # │ Helper functions │ @@ -164,7 +164,6 @@ QueryRipestat(){ StatusbarMessage "Retrieving prefix allocations and announcements for AS$1 ($found_asname)" ipv4_inetnums="" - ipv4_other_inetnums="" ipv6_inetnums="" json_ipv4_other_inetnums="" json_ipv6_other_inetnums="" @@ -208,6 +207,7 @@ QueryRipestat(){ done prefixcounter=0 + lookedup_parents_cache="" for prefix in $ipv4_ripe_prefixes; do ((prefixcounter++)) StatusbarMessage "Retrieving information for IPv4 prefix $prefixcounter/$ipv4_ripe_prefixes_count" @@ -234,13 +234,24 @@ QueryRipestat(){ parent_inetnum=$(IpcalcDeaggregate "$parent_inetnum") # check if the inetnum containing this prefix is being announced by the same AS as the prefix itself, otherwise # it means it's part of a larger supernet by some other AS (e.g. larger carrier allocating own prefix to smaller customer) - LookupASNAndRouteFromIP "$parent_inetnum" - if [ -z "$found_asname" ] || [ "$1" = "$found_asn" ]; then - # the target AS is also announcing the larger inetnum, or nobody is announcing it. Either way consider it part of the target's resources - ipv4_inetnums+="$parent_inetnum\n" + if ! grep -q "$parent_inetnum" <<<"$lookedup_parents_cache"; then + # this parent inetnum hasn't been looked up yet + lookedup_parents_cache+="$parent_inetnum\n" + LookupASNAndRouteFromIP "$parent_inetnum" + if [ -z "$found_asname" ] || [ "$1" = "$found_asn" ]; then + # the target AS is also announcing the larger inetnum, or nobody is announcing it. Either way consider it part of the target's resources + ipv4_inetnums+="$parent_inetnum\n" + else + # the larger inetnum is being announced by another AS, only add the announced (smaller) prefix to the list + ipv4_inetnums+="$prefix\n" + fi else - # the larger inetnum is being announced by another AS, only add the announced (smaller) prefix to the list - ipv4_inetnums+="$prefix\n" + # this parent inetnum has already been looked up + # if it's not present in the list of ipv4_inetnums, it means it's part of a larger supernet by some other AS. + # therefore we only add the announced (smaller) prefix to the list + if ! grep -q "$parent_inetnum" <<<"$ipv4_inetnums"; then + ipv4_inetnums+="$prefix\n" + fi fi else ipv4_inetnums+="$prefix\n" @@ -253,50 +264,110 @@ QueryRipestat(){ if [ "$ADDITIONAL_INETNUM_LOOKUP" = true ]; then # fetch further inetnums allocated to this AS from pWhois - StatusbarMessage "Identifying additional INETNUMs allocated to AS$1 (Hint: use -n to skip)" + StatusbarMessage "Identifying additional INETNUMs (not announced or announced by other AS) allocated to AS$1" pwhois_prefixes=$(PwhoisListPrefixesForOrg "$found_org") pwhois_unique_prefixes=$(comm -13 <(echo -e "$ipv4_ripe_prefixes" | sort) <(echo -e "$pwhois_prefixes" | sort)) - pwhois_unique_prefixes_count=$(wc -l <<<"$pwhois_unique_prefixes") - prefixcounter=0 - for prefix in $pwhois_unique_prefixes; do - # found a new prefix in pWhois, check if it's announced by a different AS - # (e.g. target AS has delegated announcements for this prefix to another AS) - ((prefixcounter++)) - StatusbarMessage "Identifying origin AS for additional IPv4 prefix $prefixcounter/$pwhois_unique_prefixes_count" - LookupASNAndRouteFromIP "$prefix" - prefix_originator_asn="$found_asn" - if [ -z "$prefix_originator_asn" ] || [ "$prefix_originator_asn" = "$1" ]; then - # prefix originator is same as target AS, or prefix not announced - # add this prefix to the allocated IP resources for this AS - ipv4_inetnums+="$prefix\n" - else - # this prefix is allocated to target AS, but is being announced by a different AS - prefix_originator_org=$(docurl -m5 -s "https://stat.ripe.net/data/as-overview/data.json?resource=AS${found_asn}&sourceapp=nitefood-asn" | \ - jq -r 'select (.data.holder != null) | .data.holder' | \ - awk -F' - ' '{ if ( $2 ) {print $2} else {print} }' \ - ) - if [ "$JSON_OUTPUT" = true ]; then - [[ -n "$json_ipv4_other_inetnums" ]] && json_ipv4_other_inetnums+="," - json_ipv4_other_inetnums+="{\"prefix\":\"$prefix\",\"origin_asn\":\"$prefix_originator_asn\",\"origin_org\":\"$prefix_originator_org\"}" + pwhois_unique_prefixes=$(comm -13 <(echo -e "$lookedup_parents_cache" | sort) <(echo -e "$pwhois_unique_prefixes" | sort)) + if [ -n "$pwhois_unique_prefixes" ]; then + pwhois_unique_prefixes_count=$(wc -l <<<"$pwhois_unique_prefixes") + StatusbarMessage "Identifying origin AS for $pwhois_unique_prefixes_count additional IPv4 prefix(es)" + # NEW WAY (bulk query to Team Cymru whois server) + # map the unique prefixes pWhois reported to an array + mapfile -t pwhois_unique_prefixes_array < <(echo -e "$pwhois_unique_prefixes") + # assemble a bulk Team Cymru whois lookup query for the new prefixes found in pWhois. + # we'll check if they're announced by the target AS, by different one, or by no one at all + # (e.g. target AS has delegated announcements for this prefix to another AS, or is not announcing it) + # and compile a list to integrate into the allocated IP resources for this AS + teamcymru_bulk_query="begin\n" + for prefix in $pwhois_unique_prefixes; do + teamcymru_bulk_query+="$prefix\n" + done + teamcymru_bulk_query+="end" + prefixcounter=0 + for single_prefix_data in $(echo -e "$teamcymru_bulk_query" | ncat --no-shutdown whois.cymru.com 43 | grep "|" | sed 's/\ *|\ */|/g'); do + prefix="${pwhois_unique_prefixes_array[$prefixcounter]}" + prefix_originator_asn=$(echo "$single_prefix_data" | cut -d '|' -f 1) + if [ "$prefix_originator_asn" = "$1" ]; then + # prefix originator is same as target AS, add this prefix to the allocated IP resources for this AS + ipv4_inetnums+="$prefix\n" + elif [ "$prefix_originator_asn" = "NA" ]; then + # prefix not announced, add this prefix to the allocated IP resources for this AS with a "not announced" remark + if [ "$JSON_OUTPUT" = true ]; then + [[ -n "$json_ipv4_other_inetnums" ]] && json_ipv4_other_inetnums+="," + json_ipv4_other_inetnums+="{\"prefix\":\"$prefix\",\"origin_asn\":\"\",\"origin_org\":\"\", \"is_announced\":false}" + elif [ "$IS_ASN_CHILD" = true ]; then + # skip colors, they will be added along with hyperlinks later + ipv4_inetnums+=$(printf "%-18s → not announced" "$prefix") + ipv4_inetnums+="\n" + else + ipv4_inetnums+=$(printf "${dim}%-18s → ${red}not announced${default}${green}" "$prefix") + ipv4_inetnums+="\n" + fi else - ipv4_inetnums+="$prefix (announced by AS${prefix_originator_asn} - ${prefix_originator_org})\n" + # prefix is announced by a different AS, add this prefix to the allocated IP resources for this AS + prefix_originator_org=$(echo "$single_prefix_data" | cut -d '|' -f 3) + if [ "$JSON_OUTPUT" = true ]; then + [[ -n "$json_ipv4_other_inetnums" ]] && json_ipv4_other_inetnums+="," + json_ipv4_other_inetnums+="{\"prefix\":\"$prefix\",\"origin_asn\":\"$prefix_originator_asn\",\"origin_org\":\"$prefix_originator_org\", \"is_announced\":true}" + elif [ "$IS_ASN_CHILD" = true ]; then + # skip colors, they will be added along with hyperlinks later + ipv4_inetnums+=$(printf "%-18s → announced by AS%s %s" "$prefix" "${prefix_originator_asn}" "${prefix_originator_org}") + ipv4_inetnums+="\n" + else + ipv4_inetnums+=$(printf "%-18s ${dim}→ announced by ${default}${red}AS%s ${default}${green}%s" "$prefix" "${prefix_originator_asn}" "${prefix_originator_org}") + ipv4_inetnums+="\n" + fi fi - fi - done + ((prefixcounter++)) + done + # OLD WAY (one lookup per prefix) + # for prefix in $pwhois_unique_prefixes; do + # # found a new prefix in pWhois, check if it's announced by a different AS + # # (e.g. target AS has delegated announcements for this prefix to another AS) + # ((prefixcounter++)) + # StatusbarMessage "Identifying origin AS for additional IPv4 prefix $prefixcounter/$pwhois_unique_prefixes_count" + # LookupASNAndRouteFromIP "$prefix" + # prefix_originator_asn="$found_asn" + # if [ -z "$prefix_originator_asn" ] || [ "$prefix_originator_asn" = "$1" ]; then + # # prefix originator is same as target AS, or prefix not announced + # # add this prefix to the allocated IP resources for this AS + # ipv4_inetnums+="$prefix\n" + # else + # # this prefix is allocated to target AS, but is being announced by a different AS + # prefix_originator_org=$(docurl -m5 -s "https://stat.ripe.net/data/as-overview/data.json?resource=AS${found_asn}&sourceapp=nitefood-asn" | \ + # jq -r 'select (.data.holder != null) | .data.holder' | \ + # awk -F' - ' '{ if ( $2 ) {print $2} else {print} }' \ + # ) + # if [ "$JSON_OUTPUT" = true ]; then + # [[ -n "$json_ipv4_other_inetnums" ]] && json_ipv4_other_inetnums+="," + # json_ipv4_other_inetnums+="{\"prefix\":\"$prefix\",\"origin_asn\":\"$prefix_originator_asn\",\"origin_org\":\"$prefix_originator_org\"}" + # else + # ipv4_inetnums+="$prefix (announced by AS${prefix_originator_asn} - ${prefix_originator_org})\n" + # fi + # fi + # done + fi fi if [ -n "$ipv4_inetnums" ]; then - ipv4_inetnums=$(echo -e "$ipv4_inetnums" | sort -u) + ipv4_inetnums=$(echo -e "$ipv4_inetnums" | sort -iu) if [ "$IS_ASN_CHILD" = true ] && [ "$JSON_OUTPUT" = false ]; then + # HTML output html="" for inetnum in $ipv4_inetnums; do if grep -q "announced by" <<<"$inetnum"; then - # handle special case " (announced by AS)" - actual_inetnum=$(cut -d ' ' -f 1 <<<"$inetnum") - originator=$(cut -d ' ' -f 4 <<<"$inetnum") - rest_of_line=$(cut -d ' ' -f 5- <<<"$inetnum") - html+="$actual_inetnum (announced by " - html+="$originator ${rest_of_line}\n" + # handle special case " → announced by AS" + actual_inetnum=${inetnum:0:18} + originator=$(cut -d ' ' -f 7 <<<"$inetnum") + rest_of_line=$(cut -d ' ' -f 8- <<<"$inetnum") + html+="$actual_inetnum" + html+=" → announced by " + html+="$originator ${rest_of_line}\n" + elif grep -q "not announced" <<<"$inetnum"; then + # handle special case " → not announced" + actual_inetnum=${inetnum:0:18} + html+="$actual_inetnum" + html+=" → not announced\n" else html+="$inetnum\n" fi @@ -314,9 +385,6 @@ QueryRipestat(){ ipv6_inetnums="$html" fi fi - if [ -n "$ipv4_other_inetnums" ]; then - ipv4_other_inetnums=$(echo -e "$ipv4_other_inetnums" | sort -u) - fi if [ "$JSON_OUTPUT" = true ]; then json_ipv4_aggregated_inetnums=$(jq -cM --slurp --raw-input 'split("\n") | map(select(length > 0))' <<<"$ipv4_inetnums") json_ipv6_aggregated_inetnums=$(jq -cM --slurp --raw-input 'split("\n") | map(select(length > 0))' <<<"$ipv6_inetnums") @@ -631,17 +699,29 @@ LookupASNAndRouteFromIP(){ GetCAIDARank "$found_asn" fi else - # DNS query (faster) for IPv4 addresses - rev=$(echo "$1" | awk -F'.' '{printf $4 "." $3 "." $2 "." $1}') - output=$(host -t TXT "$rev.origin.asn.cymru.com" | awk -F'"' 'NR==1{print $2}' | sed 's/\ *|\ */|/g') - found_asn=$(echo "$output" | awk -F'[|]' 'NR==1{print $1}' | cut -d ' ' -f 1) # final cut gets first origin AS only if cymru has multiple - if [ -n "$found_asn" ]; then - found_asname=$(host -t TXT "AS$found_asn.asn.cymru.com" | grep -v "NXDOMAIN" | awk -F'|' 'NR==1{print substr($NF,2,length($NF)-2)}') - found_route=$(echo "$output" | awk -F'[|]' 'NR==1{print $2}') + # Query RIPEStat for IPv4 addresses + output=$(docurl -m5 -s "https://stat.ripe.net/data/prefix-overview/data.json?resource=$1&sourceapp=nitefood-asn") + if jq -r '.data.announced' <<<"$output" | grep -q "true"; then + found_asn=$(jq -r '.data.asns[0].asn' <<<"$output") + found_asname=$(jq -r '.data.asns[0].holder' <<<"$output") + # look up the country this ASN is located in + country=$(docurl -m5 -s "https://stat.ripe.net/data/rir-stats-country/data.json?resource=AS${found_asn}" | jq -r '.data.located_resources[0].location') + [[ "$country" != "null" ]] && found_asname="${found_asname}, ${country}" + found_route=$(jq -r '.data.resource' <<<"$output") UNANNOUNCED_PREFIX=false else - # Team Cymru has no data for this IPv4. Inform WhoisIP() that we will have to fall back to a generic whois lookup. - UNANNOUNCED_PREFIX=true + # RIPEStat has no data for this IPv4. Fallback to Team Cymru DNS query (faster than whois) + rev=$(echo "$1" | cut -d '/' -f 1 | awk -F'.' '{printf $4 "." $3 "." $2 "." $1}') + output=$(host -t TXT "$rev.origin.asn.cymru.com" | awk -F'"' 'NR==1{print $2}' | sed 's/\ *|\ */|/g') + found_asn=$(echo "$output" | awk -F'[|]' 'NR==1{print $1}' | cut -d ' ' -f 1) # final cut gets first origin AS only if cymru has multiple + if [ -n "$found_asn" ]; then + found_asname=$(host -t TXT "AS$found_asn.asn.cymru.com" | grep -v "NXDOMAIN" | awk -F'|' 'NR==1{print substr($NF,2,length($NF)-2)}') + found_route=$(echo "$output" | awk -F'[|]' 'NR==1{print $2}') + UNANNOUNCED_PREFIX=false + else + # Team Cymru has no data for this IPv4 either. Inform WhoisIP() that we will have to fall back to a generic whois lookup. + UNANNOUNCED_PREFIX=true + fi fi fi else @@ -689,7 +769,8 @@ PrintUsage(){ "\n $script_name [${red}-v${default}] ${red}-l${default} [${red}SERVER OPTIONS${default}]" \ "\n\nOPTIONS:" \ "\n\n ${green}-t (enable trace)\n\t${default}Enable AS path trace to the ${blue}TARGET${default} (this is the default behavior)" \ - "\n\n ${green}-n (no trace)\n\t${default}Disable tracing the AS path to the ${blue}TARGET${default}" \ + "\n\n ${green}-n (no trace|no additional INETNUM lookups)\n\t${default}Disable tracing the AS path to the ${blue}TARGET${default} (for IP targets) or" \ + "\n\tDisable additional (unannounced / announced by other AS) INETNUM lookups for the ${blue}TARGET${default} (for AS targets)" \ "\n\n ${green}-d (detailed)\n\t${default}Output detailed hop info during the AS path trace to the ${blue}TARGET${default}" \ "\n\tThis option also enables RPKI validation/BGP hijacking detection for every hop" \ "\n\n ${green}-a (ASN Suggest)\n\t${default}Lookup AS names and numbers matching ${blue}TARGET${default}" \ @@ -2543,11 +2624,16 @@ PwhoisListPrefixesForOrg(){ org="$1" full_org_search_data=$(whois -h whois.pwhois.org "registry org-name=$org") orgids=$(echo -e "$full_org_search_data" | grep -i -E -B1 "Org-Name: $org$" | grep "Org-ID" | cut -d ':' -f 2- | sed 's/^ //g') + # assemble bulk pWhois query + pwhois_bulk_query="begin\n" for orgid in $orgids; do - for prefix in $(whois -h whois.pwhois.org "netblock org-id=${orgid}" | grep -E "^\*>"); do - iprange=$(echo -e "$prefix" | cut -d '>' -f 2 | cut -d '|' -f 1 | tr -d ' ') - IpcalcDeaggregate "$iprange" - done + pwhois_bulk_query+="netblock org-id=${orgid}\n" + done + pwhois_bulk_query+="end" + # query pWhois + for prefix in $(echo -e "$pwhois_bulk_query" | ncat whois.pwhois.org 43 | grep -E "^\*>"); do + iprange=$(echo -e "$prefix" | cut -d '>' -f 2 | cut -d '|' -f 1 | tr -d ' ') + IpcalcDeaggregate "$iprange" done } @@ -3702,9 +3788,11 @@ htmlwhite="#cccccc" htmlblack="#1e1e1e" htmllightgray="#d5d5d5" htmlred="#ff5f5f" +htmldarkred="#b74d4d" htmlblue="#00afd7" htmlyellow="#afaf00" htmlgreen="#00af5f" +htmldarkgreen="#058505" htmlmagenta="#ff5fff" [[ "$TERM" = "dumb" ]] && IS_HEADLESS=true || IS_HEADLESS=false