Many updates
This commit is contained in:
@@ -116,7 +116,7 @@ class NetworkRange < ApplicationRecord
|
||||
|
||||
# Parent/child relationships
|
||||
def parent_ranges
|
||||
NetworkRange.where("network << ?::inet AND masklen(network) < ?", network.to_s, prefix_length)
|
||||
NetworkRange.where("?::inet << network AND masklen(network) < ?", network.to_s, prefix_length)
|
||||
.order("masklen(network) DESC")
|
||||
end
|
||||
|
||||
@@ -142,6 +142,59 @@ class NetworkRange < ApplicationRecord
|
||||
.first
|
||||
end
|
||||
|
||||
# Check if this network or any parent has IPAPI data
|
||||
def has_ipapi_data_available?
|
||||
return true if has_network_data_from?(:ipapi)
|
||||
|
||||
parent_ranges.any? { |parent| parent.has_network_data_from?(:ipapi) }
|
||||
end
|
||||
|
||||
# Generic API fetching status management
|
||||
def is_fetching_api_data?(source)
|
||||
fetching_status = network_data&.dig('fetching_status') || {}
|
||||
fetching_status[source.to_s] &&
|
||||
fetching_status[source.to_s]['started_at'] &&
|
||||
fetching_status[source.to_s]['started_at'] > 5.minutes.ago.to_f
|
||||
end
|
||||
|
||||
def mark_as_fetching_api_data!(source)
|
||||
self.network_data ||= {}
|
||||
self.network_data['fetching_status'] ||= {}
|
||||
self.network_data['fetching_status'][source.to_s] = {
|
||||
'started_at' => Time.current.to_f,
|
||||
'job_id' => SecureRandom.hex(8)
|
||||
}
|
||||
save!
|
||||
end
|
||||
|
||||
def clear_fetching_status!(source)
|
||||
if network_data&.dig('fetching_status')&.dig(source.to_s)
|
||||
self.network_data['fetching_status'].delete(source.to_s)
|
||||
# Clean up empty fetching_status hash
|
||||
self.network_data.delete('fetching_status') if self.network_data['fetching_status'].empty?
|
||||
save!
|
||||
end
|
||||
end
|
||||
|
||||
# Check if we should fetch API data (not available and not currently being fetched)
|
||||
def should_fetch_api_data?(source)
|
||||
return false if send("has_network_data_from?(#{source})") if respond_to?("has_network_data_from?(#{source})")
|
||||
return false if is_fetching_api_data?(source)
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
# Check if this network or any parent has IPAPI data available and no active fetch
|
||||
def should_fetch_ipapi_data?
|
||||
return false if has_ipapi_data_available?
|
||||
return false if is_fetching_api_data?(:ipapi)
|
||||
|
||||
# Also check if any parent is currently fetching IPAPI data
|
||||
return false if parent_ranges.any? { |parent| parent.is_fetching_api_data?(:ipapi) }
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def inherited_intelligence
|
||||
return own_intelligence if has_intelligence?
|
||||
|
||||
@@ -168,6 +221,12 @@ class NetworkRange < ApplicationRecord
|
||||
}
|
||||
end
|
||||
|
||||
def agent_tally
|
||||
# Rails.cache.fetch("#{to_s}:agent_tally", expires_in: 5.minutes) do
|
||||
events.map(&:user_agent).tally
|
||||
# end
|
||||
end
|
||||
|
||||
# Geographic lookup
|
||||
def geo_lookup_country!
|
||||
return if country.present?
|
||||
@@ -189,6 +248,12 @@ class NetworkRange < ApplicationRecord
|
||||
where("network && ?", range_cidr)
|
||||
end
|
||||
|
||||
def self.findd(cidr)
|
||||
cidr = cidr.gsub("_", "/")
|
||||
cidr = "#{cidr}/24" unless cidr.include?("/")
|
||||
find_by(network: cidr)
|
||||
end
|
||||
|
||||
def self.find_or_create_by_cidr(cidr, user: nil, source: nil, reason: nil)
|
||||
find_or_create_by(network: cidr) do |range|
|
||||
range.user = user
|
||||
@@ -246,6 +311,63 @@ class NetworkRange < ApplicationRecord
|
||||
network_data&.key?(source.to_s) && network_data[source.to_s].present?
|
||||
end
|
||||
|
||||
# IPAPI tracking at /24 granularity
|
||||
# Find or create the /24 network for a given IP address
|
||||
def self.find_or_create_tracking_network_for_ip(ip_address)
|
||||
ip = IPAddr.new(ip_address.to_s)
|
||||
|
||||
# Create /24 network for IPv4, /64 for IPv6
|
||||
tracking_cidr = if ip.ipv4?
|
||||
"#{ip.mask(24)}/24"
|
||||
else
|
||||
"#{ip.mask(64)}/64"
|
||||
end
|
||||
|
||||
find_or_create_by(network: tracking_cidr) do |range|
|
||||
range.source = 'auto_generated'
|
||||
range.creation_reason = 'IPAPI tracking network'
|
||||
end
|
||||
end
|
||||
|
||||
# Check if we should fetch IPAPI data for a given IP address
|
||||
# Uses /24 networks as the tracking unit
|
||||
def self.should_fetch_ipapi_for_ip?(ip_address)
|
||||
tracking_network = find_or_create_tracking_network_for_ip(ip_address)
|
||||
|
||||
# Check if /24 has been queried recently
|
||||
queried_at = tracking_network.network_data&.dig('ipapi_queried_at')
|
||||
return true if queried_at.nil?
|
||||
|
||||
# Check if IPAPI returned a CIDR that actually covers this IP
|
||||
# (handles edge case where IPAPI returns /25 or more specific)
|
||||
returned_cidr = tracking_network.network_data&.dig('ipapi_returned_cidr')
|
||||
if returned_cidr.present?
|
||||
begin
|
||||
returned_range = IPAddr.new(returned_cidr)
|
||||
ip = IPAddr.new(ip_address.to_s)
|
||||
# If the IP is NOT covered by what IPAPI returned, fetch again
|
||||
return true unless returned_range.include?(ip)
|
||||
rescue IPAddr::InvalidAddressError => e
|
||||
Rails.logger.warn "Invalid CIDR stored in ipapi_returned_cidr: #{returned_cidr}"
|
||||
end
|
||||
end
|
||||
|
||||
# Re-query after 1 year
|
||||
Time.at(queried_at) < 1.year.ago
|
||||
rescue => e
|
||||
Rails.logger.error "Error checking IPAPI fetch status for #{ip_address}: #{e.message}"
|
||||
true # Default to fetching on error
|
||||
end
|
||||
|
||||
# Mark that we've queried IPAPI for this /24 network
|
||||
# @param returned_cidr [String] The CIDR that IPAPI actually returned (may be more specific than /24)
|
||||
def mark_ipapi_queried!(returned_cidr)
|
||||
self.network_data ||= {}
|
||||
self.network_data['ipapi_queried_at'] = Time.current.to_i
|
||||
self.network_data['ipapi_returned_cidr'] = returned_cidr
|
||||
save!
|
||||
end
|
||||
|
||||
# String representations
|
||||
def to_s
|
||||
cidr
|
||||
@@ -261,10 +383,12 @@ class NetworkRange < ApplicationRecord
|
||||
self[:events_count] || 0
|
||||
end
|
||||
|
||||
def events
|
||||
Event.where("ip_address <<= ?", cidr)
|
||||
end
|
||||
|
||||
def recent_events(limit: 100)
|
||||
Event.where(ip_address: child_ranges.pluck(:network_address) + [network_address])
|
||||
.recent
|
||||
.limit(limit)
|
||||
events.recent.limit(limit)
|
||||
end
|
||||
|
||||
def blocking_rules
|
||||
|
||||
Reference in New Issue
Block a user