# frozen_string_literal: true # # Copyright (c) 2006-2023 Hal Brodigan (postmodern.mod3 at gmail.com) # # ronin-support is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ronin-support is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with ronin-support. If not, see . # require 'ronin/support/network/exceptions' require 'ronin/support/network/asn' require 'ronin/support/network/dns' require 'ronin/support/network/host' require 'ronin/support/text/patterns' require 'ipaddr' require 'socket' require 'net/https' module Ronin module Support module Network # # Represents a single IP address. # # ## Examples # # ip = IP.new('192.30.255.113') # # Reverse DNS lookup: # # ip.get_name # # => "lb-192-30-255-113-sea.github.com" # ip.get_names # # => ["lb-192-30-255-113-sea.github.com"] # ip.get_host # # => # # ip.get_hosts # # => [#] # ip.host # # => # # ip.hosts # # => [#] # # Get ASN information: # # ip.asn # # => #> # # @api public # # @since 1.0.0 # class IP < IPAddr # The address of the IP. # # @return [String] attr_reader :address # # Initializes the IP address. # # @param [String] address # The address of the IP. # # @param [Integer] family # The address family for the CIDR range. This is mainly for # backwards compatibility with `IPAddr#initialize`. # # @raise [InvalidIP] # The given address is not a valid IP address. # # @example # ip = IP.new('192.30.255.113') # # @note # If the IP address has an `%iface` suffix, it will be removed from # the IP address. # def initialize(address,family=Socket::AF_UNSPEC) # XXX: remove the %iface suffix for ruby < 3.1.0 if address.kind_of?(String) && address =~ /%.+$/ address = address.sub(/%.+$/,'') end begin super(address,family) rescue IPAddr::InvalidAddressError raise(InvalidIP,"invalid IP address: #{address.inspect}") end @address = case address when String then address.to_s else to_s end end # The URI for https://ipinfo.io/ip IPINFO_URI = URI::HTTPS.build(host: 'ipinfo.io', path: '/ip') # # Determines the current public IP address. # # @return [String, nil] # The public IP address according to {https://ipinfo.io/ip}. # def self.public_address response = begin Net::HTTP.get_response(IPINFO_URI) rescue # ignore any network failures end if response && response.code == '200' return response.body end end # # Determines the current public IP. # # @return [IP, nil] # The public IP according to {https://ipinfo.io/ip}. # def self.public_ip if (address = public_address) new(address) end end # # Determines all local IP addresses. # # @return [Array] # # @example # IP.local_addresses # # => ["127.0.0.1", "192.168.1.42", "::1", "fe80::4ba:612f:9e2:37e2"] # def self.local_addresses Socket.ip_address_list.map do |addrinfo| address = addrinfo.ip_address if address =~ /%.+$/ address = address.sub(/%.+$/,'') end address end end # # Determines all local IPs. # # @return [Array] # # @example # IP.local_ips # # => [#, # #, # #, # #] # def self.local_ips local_addresses.map(&method(:new)) end # # Determines the local IP address. # # @return [String] # # @example # IP.local_address # # => "127.0.0.1" # def self.local_address Socket.ip_address_list.first.ip_address end # # Determines the local IP. # # @return [IP] # # @example # IP.local_ip # # => # # def self.local_ip new(local_address) end # # Extracts IP Addresses from text. # # @param [String] text # The text to scan for IP Addresses. # # @param [4, :v4, :ipv4, 6, :v6, :ipv6] version # The version of IP Address to extract. # # @yield [ip] # The given block will be passed each extracted IP Address. # # @yieldparam [String] ip # An IP Address from the text. # # @return [Array] # The IP Addresses found in the text. # # @example # IPAddr.extract("Host: 127.0.0.1\n\rHost: 10.1.1.1\n\r") # # => ["127.0.0.1", "10.1.1.1"] # # @example Extract only IPv4 addresses from a large amount of text: # IPAddr.extract(text,:v4) do |ip| # puts ip # end # def self.extract(text,version=nil,&block) return enum_for(__method__,text,version).to_a unless block_given? regexp = case version when :ipv4, :v4, 4 then Text::Patterns::IPV4_ADDR when :ipv6, :v6, 6 then Text::Patterns::IPV6_ADDR else Text::Patterns::IP_ADDR end text.scan(regexp,&block) return nil end # # Determines if the address is a IPv4 broadcast addresses. # # @return [Boolean] # # @example # ip = IPAddr.new('255.255.255.255') # ip.broadcast? # # => true # ip = IPAddr.new('192.168.1.255') # ip.broadcast? # # => true # ip = IPAddr.new('1.1.1.1') # ip.broadcast? # # => false # def broadcast? # NOTE: IPv6 does not have broadcast addresses ipv4? && (@addr & 0xff) == 0xff end # # Determines if the address is a "logical" IPv4 address. # # @return [Boolean] # # @example # ip = IPAddr.new('0.0.0.0') # ip.logical? # # => true # ip = IPAddr.new('192.168.1.0') # ip.logical? # # => true # ip = IPAddr.new('1.1.1.1') # ip.logical? # # => false # def logical? ipv4? && (@addr & 0xff) == 0x00 end # # The Autonomous System Number (ASN) information for the IP address. # # @return [ASN::Record] # # @example # ip = IP.new('93.184.216.34') # ip.asn # # => #> # def asn @asn ||= ASN.query(self) end # # Looks up the hostname of the address. # # @param [Hash{Symbol => Object}] kwargs # Additional keyword arguments. # # @option [Array, String, nil] :nameservers # Optional DNS nameserver(s) to query. # # @option [String, nil] :nameserver # Optional DNS nameserver to query. # # @return [String, nil] # The hostname of the address. # # @example # ip = IP.new('192.30.255.113') # ip.get_name # # => "lb-192-30-255-113-sea.github.com" # def get_name(**kwargs) DNS.get_name(@address,**kwargs) end alias reverse_lookup get_name # # Looks up all hostnames associated with the IP. # # @param [Hash{Symbol => Object}] kwargs # Additional keyword arguments. # # @option [Array, String, nil] :nameservers # Optional DNS nameserver(s) to query. # # @option [String, nil] :nameserver # Optional DNS nameserver to query. # # @return [Array] # The hostnames of the address. # # @example # ip = IP.new('192.30.255.113') # ip.get_names # # => ["lb-192-30-255-113-sea.github.com"] # def get_names(**kwargs) DNS.get_names(@address,**kwargs) end # # Looks up the host for the IP. # # @param [Hash{Symbol => Object}] kwargs # Additional keyword arguments. # # @option [Array, String, nil] :nameservers # Optional DNS nameserver(s) to query. # # @option [String, nil] :nameserver # Optional DNS nameserver to query. # # @return [Host, nil] # The host for the IP. # # @example # ip = IP.new('192.30.255.113') # ip.get_host # # => # # def get_host(**kwargs) if (name = get_name(**kwargs)) Host.new(name) end end # # Looks up all hosts associated with the IP. # # @param [Hash{Symbol => Object}] kwargs # Additional keyword arguments. # # @option [Array, String, nil] :nameservers # Optional DNS nameserver(s) to query. # # @option [String, nil] :nameserver # Optional DNS nameserver to query. # # @return [Array] # The hosts for the IP. # # @example # ip = IP.new('192.30.255.113') # ip.get_hosts # # => [#] # def get_hosts(**kwargs) get_names(**kwargs).map { |name| Host.new(name) } end # # The host names of the address. # # @return [Array] # The host names of the address or an empty Array if the IP address # has no host names. # # @example # ip = IP.new('192.30.255.113') # ip.hosts # # => [#] # # @note This method returns memoized data. # def hosts @hosts ||= get_hosts end # # The primary host name of the address. # # @return [Host, nil] # The host name or `nil` if the IP address has no host names. # # @example # ip = IP.new('192.30.255.113') # ip.host # # => # # def host hosts.first end # # Queries the first `PTR` DNS record for the IP address. # # @param [Hash{Symbol => Object}] kwargs # Additional keyword arguments. # # @option [Array, String, nil] :nameservers # Optional DNS nameserver(s) to query. # # @option [String, nil] :nameserver # Optional DNS nameserver to query. # # @return [Resolv::DNS::Resource::PTR, nil] # The first `PTR` DNS record of the host name or `nil` if the host # name has no `PTR` records. # # @see https://rubydoc.info/stdlib/resolv/Resolv/DNS/Resource/PTR # def get_ptr_record(**kwargs) DNS.get_ptr_record(@address,**kwargs) end # # Queries the `PTR` host name for the IP address. # # @param [Hash{Symbol => Object}] kwargs # Additional keyword arguments. # # @option [Array, String, nil] :nameservers # Optional DNS nameserver(s) to query. # # @option [String, nil] :nameserver # Optional DNS nameserver to query. # # @return [String, nil] # The host name that points to the given IP. # def get_ptr_name(**kwargs) DNS.get_ptr_name(@address,**kwargs) end # # Queries all `PTR` DNS records for the IP address. # # @param [Hash{Symbol => Object}] kwargs # Additional keyword arguments. # # @option [Array, String, nil] :nameservers # Optional DNS nameserver(s) to query. # # @option [String, nil] :nameserver # Optional DNS nameserver to query. # # @return [Array] # All `PTR` DNS records for the given IP. # # @see https://rubydoc.info/stdlib/resolv/Resolv/DNS/Resource/PTR # def get_ptr_records(**kwargs) DNS.get_ptr_records(@address,**kwargs) end # # Queries all `PTR` names for the IP address. # # @param [Hash{Symbol => Object}] kwargs # Additional keyword arguments. # # @option [Array, String, nil] :nameservers # Optional DNS nameserver(s) to query. # # @option [String, nil] :nameserver # Optional DNS nameserver to query. # # @return [Array] # The `PTR` names for the given IP. # def get_ptr_names(**kwargs) DNS.get_ptr_names(@address,**kwargs) end alias canonical to_string alias to_uint to_i alias to_str to_s # # Inspects the IP. # # @return [String] # The inspected IP object. # def inspect "#<#{self.class}: #{@address}>" end end end end end