2012-07-13 71 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