Rails3 + Mongoid を使用して、Mongodb データベースに IPv6 アドレスを保存する必要があります。
コレクションには (ほとんどの場合) IPv4 アドレスもあります。
ネットワークに属するアドレスを照会する必要があるため、アドレスを 10 進数として格納する必要があります (ネットワークとアドレスを個別のコレクションに格納します)。
これらのアドレスを格納するために BigDecimals を使用しました (IPv6 アドレスは 128 ビット長であるため) が、どのアドレスがネットワークに属しているか (具体的には、ネットワーク アドレスとブロードキャスト アドレスの間) を見つけようとすると、有効な解決策が見つかりません。
Mongoid の "gte" と "lte" は整数 (BigDecimals は実際には文字列) でのみ機能するようで、空のリストを返します。文字列の範囲について mongoid モデルをクエリする方法が見つかりません。
MongoDB ではこれが許可されているようですが ( http://www.mongodb.org/display/DOCS/min+and+max+Query+Specifiers )、対応する構文が mongoid doc に見つかりません。
次のようなクエリでは、ひどい「db assertion failure」が発生します。
Network.min(address: ip.network.to_i.to_s).max(address: ip.broadcast.to_i.to_s)
「ip.to_i.to_s」は、IPAddress gem を使用しているため、10 進アドレスの文字列表現を提供します。
「to_i」または「BigDecimal.new(ip.network.to_i)」だけで同じエラーが発生する
もう 1 つの解決策は、v6 アドレスを 2 つの 64 ビット整数に格納することですが、範囲クエリの場合ははるかに複雑になるため、v6 アドレスと v4 アドレスに同じ動作を使用したいと考えています。
データベースでIPv6アドレスのクエリを処理するためのクリーンな方法を経験した人はいますか?
ここに私の現在のネットワークモデルがあります:
class Network
# INCLUSIONS
include Mongoid::Document
include Mongoid::Timestamps
# RELATIONS
belongs_to :vlan
# FIELDS
field :description
field :address, type: BigDecimal
field :prefix, type: Integer
field :v6, type: Boolean
field :routed, type: Boolean
# VALIDATIONS
validates :ip,
presence: true
# Address must be a valid IP address
validate :ip do
errors.add(:ip, :invalid) unless ip? && ip == ip.network
end
# INSTANCE METHODS
# Returns string representation of the address
def address
ip.to_s if ip
end
def address= value
raise NoMethodError, 'address can not be set directly'
end
# Provides the IPAddress object
def ip
unless @ip.is_a?(IPAddress) || self[:address].blank?
# Generate IP address
if self[:v6]
@ip = IPAddress::IPv6.parse_u128 self[:address].to_i
else
@ip = IPAddress::IPv4.parse_u32 self[:address].to_i
end
# Set IP prefix
@ip.prefix = self[:prefix] if self[:prefix]
end
@ip
end
# Sets network IP
def ip= value
value = value.to_s
@ip = value
self[:address] = nil
self[:prefix] = nil
self[:v6] = nil
begin
@ip = IPAddress value
self[:address] = @ip.to_i
self[:prefix] = @ip.prefix
self[:v6] = @ip.ipv6?
rescue
end
end
# Whether IP is a IPAddress object
def ip?
ip.is_a? IPAddress
end
# Provides network prefix
def prefix
return ip.prefix if ip?
self[:prefix]
end
def prefix= value
raise NoMethodError, 'prefix can not be set directly'
end
# Provides string representation of the network
def to_s
ip? ? ip.to_string : @ip.to_s
end
def subnets
networks = Network.min(address: ip.network.to_i.to_s).max(address: ip.broadcast.to_i.to_s)
return networks
end
end
サブネット メソッドは、現在のネットワークにネストされたネットワークを検出するために、私が取り組んでいるものです。
ネットワーク/サブネットと今後のホストアドレスとの間の「強い」データベース関係を避けて、それらを動的に保ちたいことに注意してください。
アップデート:
これが、ネストされた IP ネットワークを管理するために正常に動作する最後のクラスです。
アドレスは、16 進数の固定長文字列として格納されます。これらは、実際のアドレス サイズに一致するように base 32 で格納できますが、読みやすさのためには 16 進数の方が優れています。
Subnets メソッドは、現在のネットワークのすべてのサブネットのリストを提供します。
class Network
# INCLUSIONS
include Mongoid::Document
include Mongoid::Timestamps
# RELATIONS
belongs_to :vlan
# FIELDS
field :description
field :address, type: String
field :prefix, type: Integer
field :routed, type: Boolean
field :v6, type: Boolean
# VALIDATIONS
validates :ip,
presence: true
# Address must be a valid IP address
validate do
errors.add(:ip, :invalid) unless ip? && ip == ip.network
end
validate do
errors.add(:ip, :prefix_invalid_v6) if ip && ip.ipv6? && (self[:prefix] < 0 || self[:prefix] > 64)
end
# INSTANCE METHODS
# Returns string representation of the address
def address
ip.to_s if ip
end
def address= value
raise NoMethodError, 'address can not be set directly'
end
# Provides the IPAddress object
def ip
unless @ip.is_a?(IPAddress) || self[:address].blank?
# Generate IP address
if v6
@ip = IPAddress::IPv6.parse_u128 self[:address].to_i(16)
else
@ip = IPAddress::IPv4.parse_u32 self[:address].to_i(16)
end
# Set IP prefix
@ip.prefix = self[:prefix] if self[:prefix]
end
@ip
end
# Sets network IP
def ip= value
value = value.to_s
@ip = value
self[:address] = nil
self[:prefix] = nil
self[:v6] = nil
begin
@ip = IPAddress value
self[:address] = @ip.to_i.to_s(16).rjust((@ip.ipv4? ? 8 : 32), '0')
self[:prefix] = @ip.prefix
self[:v6] = @ip.ipv6?
rescue
end
end
# Whether IP is a IPAddress object
def ip?
ip.is_a? IPAddress
end
# Provides network prefix
def prefix
return ip.prefix if ip?
self[:prefix]
end
def prefix= value
raise NoMethodError, 'prefix can not be set directly'
end
# Provides string representation of the network
def to_s
ip? ? ip.to_string : @ip.to_s
end
# Provides nested subnets list
def subnets
length= ip.ipv4? ? 8 : 32
networks = Network.where(
v6: v6,
:address.gte => (ip.network.to_i.to_s(16)).rjust(length, '0'),
:address.lte => (ip.broadcast.to_i.to_s(16).rjust(length, '0')),
:prefix.gte => ip.prefix
).asc(:address).asc(:prefix)
end
end