Find supernets, don't create them
This commit is contained in:
@@ -136,33 +136,6 @@ class NetworkRange < ApplicationRecord
|
|||||||
.order("masklen(network) ASC") # Least specific child first
|
.order("masklen(network) ASC") # Least specific child first
|
||||||
end
|
end
|
||||||
|
|
||||||
# Find or create an ancestor network at a specific prefix length
|
|
||||||
# For example, given 192.168.1.0/24 and prefix 16, returns 192.168.0.0/16
|
|
||||||
def find_or_create_ancestor_at_prefix(target_prefix)
|
|
||||||
return self if prefix_length <= target_prefix
|
|
||||||
|
|
||||||
# Use PostgreSQL's set_masklen to create the ancestor CIDR
|
|
||||||
result = self.class.connection.execute(
|
|
||||||
"SELECT set_masklen('#{network}'::inet, #{target_prefix})::text as ancestor_cidr"
|
|
||||||
).first
|
|
||||||
|
|
||||||
return self unless result
|
|
||||||
|
|
||||||
ancestor_cidr = result["ancestor_cidr"]
|
|
||||||
return self if ancestor_cidr == cidr
|
|
||||||
|
|
||||||
# Find or create the ancestor network range
|
|
||||||
ancestor = NetworkRange.find_by(network: ancestor_cidr)
|
|
||||||
|
|
||||||
if ancestor.nil?
|
|
||||||
# Create a virtual ancestor (not persisted, just for reference)
|
|
||||||
# The caller can decide whether to persist it
|
|
||||||
ancestor = NetworkRange.new(network: ancestor_cidr, source: 'inherited')
|
|
||||||
end
|
|
||||||
|
|
||||||
ancestor
|
|
||||||
end
|
|
||||||
|
|
||||||
# Find nearest parent with intelligence data
|
# Find nearest parent with intelligence data
|
||||||
def parent_with_intelligence
|
def parent_with_intelligence
|
||||||
# Find all parent ranges (networks that contain this network)
|
# Find all parent ranges (networks that contain this network)
|
||||||
|
|||||||
@@ -517,41 +517,53 @@ validate :targets_must_be_array
|
|||||||
country = network_range.country || network_range.inherited_intelligence[:country]
|
country = network_range.country || network_range.inherited_intelligence[:country]
|
||||||
return network_range unless country
|
return network_range unless country
|
||||||
|
|
||||||
# Walk up from current prefix to /8 (IPv4) or /32 to /1 (IPv6)
|
# Check if this network has IPAPI data with a larger CIDR (asn.route or ipapi_returned_cidr)
|
||||||
current_prefix = network_range.prefix_length
|
ipapi_cidr = network_range.network_data&.dig('ipapi', 'asn', 'route') ||
|
||||||
max_prefix = network_range.ipv4? ? 8 : 1
|
network_range.network_data&.dig('ipapi_returned_cidr')
|
||||||
|
|
||||||
current_prefix.step(-1, -1).each do |prefix|
|
if ipapi_cidr && ipapi_cidr != network_range.cidr
|
||||||
break if prefix < max_prefix
|
# IPAPI returned a larger network - use it if it exists
|
||||||
|
existing = NetworkRange.find_by(network: ipapi_cidr)
|
||||||
candidate = network_range.find_or_create_ancestor_at_prefix(prefix)
|
if existing
|
||||||
# Check if candidate has geo data (either direct or inherited)
|
existing_country = existing.country || existing.inherited_intelligence[:country]
|
||||||
candidate_country = candidate.country || (candidate.persisted? ? candidate.inherited_intelligence[:country] : nil)
|
if existing_country == country
|
||||||
|
Rails.logger.debug "Using IPAPI CIDR #{existing.cidr} instead of #{network_range.cidr} (both #{country})"
|
||||||
# For virtual (unpersisted) ancestors, we need to check if any existing records in that range have the country
|
return existing
|
||||||
if candidate_country.nil? && !candidate.persisted?
|
|
||||||
# Query database to see if networks in this range have the same country
|
|
||||||
country_check = NetworkRange.where("network <<= ?", candidate.cidr)
|
|
||||||
.where.not(country: nil)
|
|
||||||
.select(:country)
|
|
||||||
.distinct
|
|
||||||
# If all networks in this range have the same country, we can use it
|
|
||||||
if country_check.count == 1 && country_check.first.country == country
|
|
||||||
candidate_country = country
|
|
||||||
end
|
end
|
||||||
end
|
else
|
||||||
|
# Create the IPAPI network range if it doesn't exist
|
||||||
# If ancestor has same country, use it (persist if virtual)
|
begin
|
||||||
if candidate_country == country
|
ipapi_network = NetworkRange.create!(
|
||||||
if !candidate.persisted?
|
network: ipapi_cidr,
|
||||||
candidate.save!
|
source: 'inherited',
|
||||||
Rails.logger.info "Created ancestor network range #{candidate.cidr} for country #{country}"
|
country: country
|
||||||
|
)
|
||||||
|
Rails.logger.info "Created IPAPI network range #{ipapi_cidr} for country #{country}"
|
||||||
|
return ipapi_network
|
||||||
|
rescue ActiveRecord::RecordNotUnique
|
||||||
|
# Race condition - another process created it
|
||||||
|
existing = NetworkRange.find_by(network: ipapi_cidr)
|
||||||
|
return existing || network_range
|
||||||
end
|
end
|
||||||
Rails.logger.debug "Expanded #{network_range.cidr} to #{candidate.cidr} (both #{country})"
|
|
||||||
return candidate
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Fallback: Look for existing parent networks with IPAPI data and same country
|
||||||
|
# Query for all networks that contain this network and have IPAPI data
|
||||||
|
parent_with_ipapi = NetworkRange.where(
|
||||||
|
"?::inet << network", network_range.cidr
|
||||||
|
).where(
|
||||||
|
"network_data ? 'ipapi' AND " \
|
||||||
|
"network_data -> 'ipapi' ->> 'location' ->> 'country_code' = ?",
|
||||||
|
country
|
||||||
|
).order("masklen(network) DESC").first
|
||||||
|
|
||||||
|
if parent_with_ipapi
|
||||||
|
Rails.logger.debug "Found existing IPAPI parent #{parent_with_ipapi.cidr} for #{network_range.cidr} (both #{country})"
|
||||||
|
return parent_with_ipapi
|
||||||
|
end
|
||||||
|
|
||||||
|
# No expansion possible - use original network
|
||||||
network_range
|
network_range
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user