Golang中的IPSET内存数据库实现探讨

Golang中的IPSET内存数据库实现探讨 我想实现一个类似于Linux内核ipset的快速IP查询数据库。 该数据库应存储用于DNS/HTTP RBL服务的IP黑名单。 DNS是一种成熟的二进制协议,RBL在全球许多地方都有使用。 简单的PTR检查可以返回一个反映IP是否被列入黑名单的虚假域名,如果是,还可以显示对应的列表名称。

主要问题在于IP黑名单需要存储在内存中。 当内存中存储大量IP地址时,可能会消耗大量内存,如果实现不当还会导致性能缓慢。 我不确定自己是否充分理解了Go语言的内存使用和管理,无法确定是使用简单的net.IP对象结构更好,还是使用整数或大整数的节点列表更好。

我发现了以下库,它似乎是一个不错的起点:

GitHub

ip2location/ip2location-go

使用IP2Location地理定位数据库通过IP2Location Go包查询地理定位信息。它可以用于确定国家、地区、城市、坐标、邮政编码、时区、ISP、域名…

但我对其实现方式感到困惑。 他们是如何遍历数据文件的? 将数据存储在某种结构体数组中会更明智吗? 或者使用节点列表?


更多关于Golang中的IPSET内存数据库实现探讨的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang中的IPSET内存数据库实现探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go语言中实现高效的IP黑名单内存数据库,可以采用基于位图(bitmap)和前缀树(trie)的混合方案。以下是具体实现:

核心数据结构

package main

import (
    "encoding/binary"
    "net"
    "sync"
)

// IPv4黑名单使用位图存储
type IPv4Blacklist struct {
    mu     sync.RWMutex
    bitmap []uint64
}

// IPv6黑名单使用前缀树存储
type IPv6TrieNode struct {
    children [2]*IPv6TrieNode // 0和1分支
    isBlack  bool
}

type IPv6Blacklist struct {
    mu   sync.RWMutex
    root *IPv6TrieNode
}

type IPSetDB struct {
    ipv4 *IPv4Blacklist
    ipv6 *IPv6Blacklist
}

IPv4位图实现

func NewIPv4Blacklist() *IPv4Blacklist {
    // IPv4地址空间:2^32个地址,使用位图需要2^32/8 = 512MB
    bitmapSize := (1 << 32) / 64 // 每个uint64存储64位
    return &IPv4Blacklist{
        bitmap: make([]uint64, bitmapSize),
    }
}

func (b *IPv4Blacklist) Add(ip net.IP) {
    if ip.To4() == nil {
        return
    }
    
    b.mu.Lock()
    defer b.mu.Unlock()
    
    ipInt := binary.BigEndian.Uint32(ip.To4())
    index := ipInt / 64
    bit := ipInt % 64
    b.bitmap[index] |= (1 << bit)
}

func (b *IPv4Blacklist) Contains(ip net.IP) bool {
    if ip.To4() == nil {
        return false
    }
    
    b.mu.RLock()
    defer b.mu.RUnlock()
    
    ipInt := binary.BigEndian.Uint32(ip.To4())
    index := ipInt / 64
    bit := ipInt % 64
    return (b.bitmap[index] & (1 << bit)) != 0
}

IPv6前缀树实现

func NewIPv6Blacklist() *IPv6Blacklist {
    return &IPv6Blacklist{
        root: &IPv6TrieNode{},
    }
}

func (b *IPv6Blacklist) Add(ip net.IP) {
    if ip.To16() == nil {
        return
    }
    
    b.mu.Lock()
    defer b.mu.Unlock()
    
    node := b.root
    ipBytes := ip.To16()
    
    // 遍历IPv6的128位
    for i := 0; i < 16; i++ {
        for j := 7; j >= 0; j-- {
            bit := (ipBytes[i] >> uint(j)) & 1
            if node.children[bit] == nil {
                node.children[bit] = &IPv6TrieNode{}
            }
            node = node.children[bit]
        }
    }
    node.isBlack = true
}

func (b *IPv6Blacklist) Contains(ip net.IP) bool {
    if ip.To16() == nil {
        return false
    }
    
    b.mu.RLock()
    defer b.mu.RUnlock()
    
    node := b.root
    ipBytes := ip.To16()
    
    for i := 0; i < 16; i++ {
        for j := 7; j >= 0; j-- {
            bit := (ipBytes[i] >> uint(j)) & 1
            if node.children[bit] == nil {
                return false
            }
            node = node.children[bit]
        }
    }
    return node.isBlack
}

完整IPSet数据库

func NewIPSetDB() *IPSetDB {
    return &IPSetDB{
        ipv4: NewIPv4Blacklist(),
        ipv6: NewIPv6Blacklist(),
    }
}

func (db *IPSetDB) AddIP(ipStr string) error {
    ip := net.ParseIP(ipStr)
    if ip == nil {
        return net.InvalidAddrError("invalid IP address")
    }
    
    if ip.To4() != nil {
        db.ipv4.Add(ip)
    } else {
        db.ipv6.Add(ip)
    }
    return nil
}

func (db *IPSetDB) CheckIP(ipStr string) (bool, error) {
    ip := net.ParseIP(ipStr)
    if ip == nil {
        return false, net.InvalidAddrError("invalid IP address")
    }
    
    if ip.To4() != nil {
        return db.ipv4.Contains(ip), nil
    } else {
        return db.ipv6.Contains(ip), nil
    }
}

使用示例

func main() {
    db := NewIPSetDB()
    
    // 添加黑名单IP
    db.AddIP("192.168.1.1")
    db.AddIP("2001:db8::1")
    
    // 检查IP
    blacklisted, _ := db.CheckIP("192.168.1.1")
    println("192.168.1.1 blacklisted:", blacklisted) // true
    
    blacklisted, _ = db.CheckIP("192.168.1.2")
    println("192.168.1.2 blacklisted:", blacklisted) // false
    
    blacklisted, _ = db.CheckIP("2001:db8::1")
    println("2001:db8::1 blacklisted:", blacklisted) // true
}

性能优化说明

这种实现方案的优势:

  • IPv4使用位图:O(1)查询时间,固定512MB内存占用
  • IPv6使用前缀树:O(128)查询时间,内存占用与黑名单大小成正比
  • 读写锁保证并发安全
  • 避免大量小对象分配,减少GC压力

对于RBL服务,这种实现可以支持每秒百万级别的查询,内存使用可预测且高效。

回到顶部