英文:
How to sort IP addresses in a trie table?
问题
我想在我的Go程序中编写一小段代码,用来创建一个小的“路由表”。
我正在使用左倾红黑树和http://github.com/petar/GoLLRB包,经过一番折腾,它似乎基本上可以工作,但我怀疑在创建树时没有正确地对IP前缀进行排序。我实验性地使用的“lessThan”函数如下:
func lessRoute(a, b interface{}) bool {
aNet := a.(Route).Net
bNet := b.(Route).Net
for i, a := range aNet.IP {
if a < bNet.IP[i] {
return true
}
if a > bNet.IP[i] {
return false
}
}
return false
}
(完整代码在https://gist.github.com/4283789)
这似乎给出了正确的结果,但效率不高。
在我的测试中,我添加了以下路由:
AddRouteString(tree, "10.0.0.0/8", 10)
AddRouteString(tree, "10.20.0.0/16", 20)
AddRouteString(tree, "10.21.0.0/16", 21)
然后,在查找10.22.0.1的路由时,它会先查找10.21.0.0/16和10.20.0.0/16,然后才找到正确的结果。
我应该如何对树进行排序以更快地找到正确的结果?
更新:@jnml提出了一个使IP比较更快的优秀建议(也许这是我能做的最好的),但我觉得应该有一种方法可以利用前缀长度来对树进行排序,以便可以在更少的步骤中找到匹配项。这就是我要寻找的。
英文:
I want to make a bit of code to have a small "routing table" in my Go program.
I'm using left-leaning red-black trees with the http://github.com/petar/GoLLRB package and basically it seems to work after fussing with it for a bit, however I suspect that I'm not sorting the IP prefixes correctly when I create the tree. The "lessThan" function I used experimentally is
func lessRoute(a, b interface{}) bool {
aNet := a.(Route).Net
bNet := b.(Route).Net
for i, a := range aNet.IP {
if a < bNet.IP[i] {
return true
}
if a > bNet.IP[i] {
return false
}
}
return false
}
(the full code is at https://gist.github.com/4283789 )
This seems to give me the correct results, but not very efficiently.
In my test I'm adding routes for
AddRouteString(tree, "10.0.0.0/8", 10)
AddRouteString(tree, "10.20.0.0/16", 20)
AddRouteString(tree, "10.21.0.0/16", 21)
and then when looking up a route for 10.22.0.1 it will look through 10.21.0.0/16 and 10.20.0.0/16 before finding the right result.
How should I order my tree to find the right results faster?
Update: @jnml has an excellent suggestion of how to make the IP comparison faster (and maybe that's the best I can do), but it seems to me like there'd be a way to make advantage of the prefix length to order the tree so matches can be found in less steps. That's what I am looking for.
答案1
得分: 1
我可能会这样写:
if bytes.Compare([]byte(a), []byte(b)) < 0 {
// ... 当地址 a 小于 b(按字典顺序)时要执行的操作
}
或者对于树比较器:
func lessRoute(a, b interface{}) bool {
return bytes.Compare([]byte(a.(Route).Net.IP), []byte(b.(Route).Net.IP)) < 0
}
bytes.Compare()
的文档在这里。
英文:
I would probably write:
if bytes.Compare([]byte(a), []byte(b)) < 0 {
// ... whatever to do when address a < b (lexicographically)
}
Or for the tree comparator:
func lessRoute(a, b interface{}) bool {
return bytes.Compare([]byte(a.(Route).Net.IP), []byte(b.(Route).Net.IP)) < 0
}
bytes.Compare()
documented here.
答案2
得分: 1
你所描述的听起来很像前缀树,也称为基数树或基数前缀树。这与左倾红黑树不同,通常用于路由,例如在Linux内核中。它通过前缀对树中的路由进行排序。你所描述的查找操作通常称为“最长前缀匹配”。
根据你的gist链接,我可以看到你最终意识到了这一点,因为你现在在gist中使用了二进制前缀树实现。
ipaddress-go Go库具有适用于IPv4或IPv6的地址二进制前缀树实现。
以下代码与你的gist中的代码等效。
package main
import (
"fmt"
"github.com/seancfoley/ipaddress-go/ipaddr"
)
func main() {
trie := ipaddr.AssociativeAddressTrie{}
cidrMappings := []struct {
cidr string
asn int
}{
{"10.0.0.2/8", 10},
{"10.20.0.0/14", 20},
{"10.21.0.0/16", 21},
{"192.168.0.0/16", 192},
{"192.168.2.0/24", 1922},
}
for _, mapping := range cidrMappings {
cidr := ipaddr.NewIPAddressString(mapping.cidr).GetAddress().
ToPrefixBlock().ToAddressBase()
trie.Put(cidr, mapping.asn)
}
fmt.Println("trie is", trie)
lookup(trie, "10.20.1.2")
lookup(trie, "10.22.1.2")
lookup(trie, "10.19.0.1")
lookup(trie, "10.21.0.1")
lookup(trie, "192.168.2.3")
lookup(trie, "230.0.0.1")
}
func lookup(trie ipaddr.AssociativeAddressTrie, addrStr string) {
addr := ipaddr.NewIPAddressString(addrStr).GetAddress().
ToAddressBase()
matchedNode := trie.LongestPrefixMatchNode(addr)
mappedVal := matchedNode.GetValue()
var mappedAsn int
if mappedVal != nil {
mappedAsn = mappedVal.(int)
}
fmt.Printf("%s lookup is %d\n", addr, mappedAsn)
}
输出:
trie is
○ 0.0.0.0/0 (5)
├─● 10.0.0.0/8 = 10 (3)
│ └─● 10.20.0.0/14 = 20 (2)
│ └─● 10.21.0.0/16 = 21 (1)
└─● 192.168.0.0/16 = 192 (2)
└─● 192.168.2.0/24 = 1922 (1)
10.20.1.2 lookup is 20
10.22.1.2 lookup is 20
10.19.0.1 lookup is 10
10.21.0.1 lookup is 21
192.168.2.3 lookup is 1922
230.0.0.1 lookup is 0
英文:
"there'd be a way to take advantage of the prefix length to order the tree so matches can be found in less steps"
What you are describing sounds a lot like a prefix trie, aka a radix tree or radix trie. This is different from a left-leaning red-black tree, and is commonly used for routing, such as within the Linux kernel. It orders the routes in the tree by prefix.
The lookup operation you are describing is typically called "longest prefix match".
Following the link to your gist, I can see that you eventually realized that yourself, as you are now using a binary trie implementation in the gist.
The ipaddress-go Go library has an address binary trie implementation that works with either IPv4 or IPv6.
The following code is equivalent to the code in your gist.
package main
import (
"fmt"
"github.com/seancfoley/ipaddress-go/ipaddr"
)
func main() {
trie := ipaddr.AssociativeAddressTrie{}
cidrMappings := []struct {
cidr string
asn int
}{
{"10.0.0.2/8", 10},
{"10.20.0.0/14", 20},
{"10.21.0.0/16", 21},
{"192.168.0.0/16", 192},
{"192.168.2.0/24", 1922},
}
for _, mapping := range cidrMappings {
cidr := ipaddr.NewIPAddressString(mapping.cidr).GetAddress().
ToPrefixBlock().ToAddressBase()
trie.Put(cidr, mapping.asn)
}
fmt.Println("trie is", trie)
lookup(trie, "10.20.1.2")
lookup(trie, "10.22.1.2")
lookup(trie, "10.19.0.1")
lookup(trie, "10.21.0.1")
lookup(trie, "192.168.2.3")
lookup(trie, "230.0.0.1")
}
func lookup(trie ipaddr.AssociativeAddressTrie, addrStr string) {
addr := ipaddr.NewIPAddressString(addrStr).GetAddress().
ToAddressBase()
matchedNode := trie.LongestPrefixMatchNode(addr)
mappedVal := matchedNode.GetValue()
var mappedAsn int
if mappedVal != nil {
mappedAsn = mappedVal.(int)
}
fmt.Printf("%s lookup is %d\n", addr, mappedAsn)
}
Output:
trie is
○ 0.0.0.0/0 (5)
├─● 10.0.0.0/8 = 10 (3)
│ └─● 10.20.0.0/14 = 20 (2)
│ └─● 10.21.0.0/16 = 21 (1)
└─● 192.168.0.0/16 = 192 (2)
└─● 192.168.2.0/24 = 1922 (1)
10.20.1.2 lookup is 20
10.22.1.2 lookup is 20
10.19.0.1 lookup is 10
10.21.0.1 lookup is 21
192.168.2.3 lookup is 1922
230.0.0.1 lookup is 0
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论