2012-07-13 72 views
2

我需要在使用Rails3 + Mongoid的Mongodb数据库中存储IPv6地址。Mongoid:IPv6地址存储

集合中还会有(大部分)IPv4地址。

我需要将地址存储为小数,因为我必须查询属于网络的地址(我将网络和地址存储在不同的集合中)。我使用BigDecimals来存储这些地址(因为IPv6地址是128位长),但当我试图找到哪些地址属于一个网络(具体地说:在网络和广播地址之间)时,我没有发现任何工作解。

Mongoid“GTE”和“LTE”似乎只对整数工作(BigDecimals的实际上是字符串),并返回一个空列表,我没有找到一个方法来查询我mongoid模型字符串范围。

MongoDB似乎允许这(http://www.mongodb.org/display/DOCS/min+and+max+Query+Specifiers),但我没有在mongoid文档中找到相应的语法。

查询类似下面提出了可怕的“DB断言失败”:

Network.min(address: ip.network.to_i.to_s).max(address: ip.broadcast.to_i.to_s) 

“ip.to_i.to_s”提供了十进制地址的字符串表示,因为我使用的ip地址宝石。

同样的错误只用“to_i”或“BigDecimal.new(ip.network.to_i)”

的另一个解决办法是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网络的最后一节课。

地址以十六进制格式存储为固定长度的字符串。它们可以存储在基址32中以匹配实际地址的大小,但是hexa更适合可读性。

子网方法提供了当前网络中所有子网的列表。

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 
+0

我刚刚看到Mongoid/MongoDB似乎只支持有符号的64位整数......所以甚至不可能将小数地址存储在2个无符号的64位整数中......我可以尝试使用4个32位整数,但这会是一个烂摊子... – 2012-07-13 14:49:28

回答

2

一种可能的方式来做到这一点是在网络存储在表示为二进制字符串。并且不包括无意义的尾随零(对于a/12,您保留前12位)。

例如:网络192.168.1.0实际上是11000000101010000000000100000000。你可以算出它的后面的零:110000001010100000000001,它是一个/ 24。

你可以找到它的所有子网:

Network.where(:address_bin => /^110000001010100000000001/, :v6 => false) 

和使用的索引上:address_bin如果有的话。 :v6 => false用于匹配相同类型的子网。

回到源:)


其他方式,更简单,你的问题是对于IPv6,但在IPv6中,地址的最后64位是保留于子网中的主机。例如,如果你有一个/ 40,你只剩64-40 = 24bits来处理子网(如果有的话)。

因此,您可以将网络地址存储为64位的整数,128位地址的前64位;最后64位是强制零。

+0

我试过第二种方式,但mongoid/mongodb只存储SIGNED 64位整数,所以即使网络部分也不适合整数值。 – 2012-07-16 09:59:46

+0

第一种解决方案是危险的,因为我不能确定所有尾随零必须被删除。例如:192.168.0.0/24 => 16个尾随零,只有8个被删除。 – 2012-07-16 10:03:14

+0

哦签署的问题很好,我错过了。但是在存储前检索或添加2^63时仍然可以使用它添加2^63。例如,以美分作为整数存储金额非常普遍。你可以减少创建特殊类型的屁股的痛苦。请参阅http://mongoid.org/en/mongoid/docs/documents.html#fields 的“自定义字段序列化”部分对于尾部零来说,它是隐含的,对于/ 12,您只保留前12位等等。我编辑过,不会让更多的读者感到困惑。 – 2012-07-16 12:58:29