Find supernets, don't create them

This commit is contained in:
Dan Milne
2025-12-27 11:56:19 +11:00
parent 108caf2fe6
commit e53e782223
2 changed files with 41 additions and 56 deletions

View File

@@ -136,33 +136,6 @@ class NetworkRange < ApplicationRecord
.order("masklen(network) ASC") # Least specific child first
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
def parent_with_intelligence
# Find all parent ranges (networks that contain this network)

View File

@@ -517,41 +517,53 @@ validate :targets_must_be_array
country = network_range.country || network_range.inherited_intelligence[:country]
return network_range unless country
# Walk up from current prefix to /8 (IPv4) or /32 to /1 (IPv6)
current_prefix = network_range.prefix_length
max_prefix = network_range.ipv4? ? 8 : 1
# Check if this network has IPAPI data with a larger CIDR (asn.route or ipapi_returned_cidr)
ipapi_cidr = network_range.network_data&.dig('ipapi', 'asn', 'route') ||
network_range.network_data&.dig('ipapi_returned_cidr')
current_prefix.step(-1, -1).each do |prefix|
break if prefix < max_prefix
candidate = network_range.find_or_create_ancestor_at_prefix(prefix)
# Check if candidate has geo data (either direct or inherited)
candidate_country = candidate.country || (candidate.persisted? ? candidate.inherited_intelligence[:country] : nil)
# For virtual (unpersisted) ancestors, we need to check if any existing records in that range have the country
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
if ipapi_cidr && ipapi_cidr != network_range.cidr
# IPAPI returned a larger network - use it if it exists
existing = NetworkRange.find_by(network: ipapi_cidr)
if existing
existing_country = existing.country || existing.inherited_intelligence[:country]
if existing_country == country
Rails.logger.debug "Using IPAPI CIDR #{existing.cidr} instead of #{network_range.cidr} (both #{country})"
return existing
end
end
# If ancestor has same country, use it (persist if virtual)
if candidate_country == country
if !candidate.persisted?
candidate.save!
Rails.logger.info "Created ancestor network range #{candidate.cidr} for country #{country}"
else
# Create the IPAPI network range if it doesn't exist
begin
ipapi_network = NetworkRange.create!(
network: ipapi_cidr,
source: 'inherited',
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
Rails.logger.debug "Expanded #{network_range.cidr} to #{candidate.cidr} (both #{country})"
return candidate
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
end