diff --git a/README.md b/README.md index 214ee7f..0857d44 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,22 @@ # Supernet (NOT STABLE, UNDER DEVELOPMENT) - -`supernet` is CIDR Store that can handle conflict resolution. It utilizes trie data structures to efficiently handle IPv4 and IPv6 addresses, ensuring fast conflict detection and resolution upon CIDR insertions. Designed to optimize memory usage, `supernet` can be used for different network management applications. +`Supernet` is a conflict-free CIDR Database (network store). ## Features +- **In-Place Conflict Resolution**: Automatically resolves conflicts during CIDR insertions, based on the CIDR priorities. +- **Generic Metadata**: Supports genetic metadata for CIDRs, making it easy to add custom data for CIDRs. +- **Small Memory Footprint**: designed to minimize memory usage while maintaining fast access and modification speeds. +- **IP Lookups**: Once Supernet loads all CIDRs, it's ready to lookup IP and return the associated CIDR and its Metadata. +- **Fixable CLI**: Shipped with simple and configurable CLI that can resolve conflicts in files (JSON, CSV and TSV). + -- **Dynamic Conflict Resolution**: Automatically resolves conflicts during CIDR insertions, based on the CIDR priorities. -- **Generic Metadata**: Supports genetic metadata for CIDRs, making it adaptable for various applications. -- **Optimized Memory Footprint**: designed to minimize memory usage while maintaining fast access and modification speeds. -- **Fast Lookups and Conflict Detection**: Uses trie data structures to ensure rapid conflict resolution and CIDR lookups, crucial for high-performance network environments. ## Installation -To install `supernet`, ensure you have Go installed on your machine (version 1.13 or later is recommended). Install the package by executing: +To install `Supernet`, ensure you have Go installed on your machine (version 1.13 or later is recommended). Install the package by executing: ```sh -go get github.com/khalid_nowaf/supernet +go get github.com/khalid-nowaf/supernet ``` ## Installation @@ -23,18 +24,40 @@ go get github.com/khalid_nowaf/supernet To install `supernet`, you need to have Go installed on your machine (version 1.13 or later recommended). Install the package by running: ```sh -go get github.com/khalid_nowaf/supernet +go get github.com/khalid-nowaf/supernet ``` ## Usage Below are some examples of how you can use the supernet package to manage network CIDRs: +## CLI +```shell +go run cmd/supernet/main.go resolve + +Resolve CIDR conflicts + +Arguments: + ... Input file containing CIDRs in CSV or JSON format + +Flags: + -h, --help Show context-sensitive help. + --log Print the details about the inserted CIDR and the conflicts if any + + --cidr-key="cidr" Key/Colum of the CIDRs in the file + --priority-keys=,... Keys/Columns to be used as CIDRs priorities + --fill-empty-priority Replace empty/null priority with zero value + --flip-rank-priority Make low value priority mean higher priority + --report Report only conflicted CIDRs + --output-format="csv" Output file format + --drop-keys=,... Keys/Columns to be dropped + --split-ip-versions Split the results in to separate files based on the CIDR IP version +``` ### Initializing a Supernet ```go package main import ( - "github.com/khalid_nowaf/supernet" + "github.com/khalid-nowaf/supernet" ) func main() { @@ -43,48 +66,99 @@ func main() { } ``` -### Inserting a CIDR +### Simple Inserting CIDRs and Lookup an IP + ```go +package main + import ( - "net" - "github.com/khalid_nowaf/supernet" + "fmt" + "net" + + "github.com/khalid-nowaf/supernet/pkg/supernet" ) -func main() { - sn := supernet.NewSupernet() - _, ipnet, _ := net.ParseCIDR("192.168.100.14/24") - metadata := supernet.NewDefaultMetadata() - sn.InsertCidr(ipnet, metadata) +type Network struct { + cidr string + name string + priorities []uint8 } -``` -### Searching for a CIDR -```go -import ( - "fmt" - "github.com/khalid_nowaf/supernet" -) - func main() { - sn := supernet.NewSupernet() - result, err := sn.LookupIP("192.168.100.14") - if err != nil { - fmt.Println("Error:", err) - return - } - if result != nil { - fmt.Printf("Found CIDR: %s\n", result) - } else { - fmt.Println("No matching CIDR found.") - } + + // create random Cidrs + networks := []*Network{ + {cidr: "123.123.123.0/24", name: "maybe my home network", priorities: []uint8{0, 0, 1}}, + {cidr: "123.123.123.0/23", name: "could be my home network", priorities: []uint8{0, 0, 2}}, + {cidr: "123.123.123.0/30", name: "it is my home network", priorities: []uint8{0, 0, 3}}, + } + + super := supernet.NewSupernet() + for _, network := range networks { + if _, ipnet, err := net.ParseCIDR(network.cidr); err == nil { + + metadata := supernet.NewMetadata(ipnet) + attributes is used to store any additional data about the network + metadata.Attributes["name"] = network.name + optional, it will be used to resolve conflict + if no priority add. the size of the network e.g /32, will be used as priority + so it is grunted smaller network will be not be over taken by larger network + metadata.Priority = network.priorities + result has information about the conflict and how it solve it + insertResult := super.InsertCidr(ipnet, metadata) + fmt.Println(insertResult.String()) see what happened + } + } + // get all networks (it will return conflict free networks) + nodes := super.AllCIDRS(false) + + for _, node := range nodes { + fmt.Printf("CIDR: %s, name: %s\n", supernet.NodeToCidr(node), node.Metadata().Attributes["name"]) + } + + // if you want to to lookup a an IP + ipnet, node, _ := super.LookupIP("123.123.123.16") + if ipnet != nil { + fmt.Printf("found: CIDR: %s, name: %s\n", ipnet.String(), node.Metadata().Attributes["name"]) + } + } ``` +**Output** + +```shell +## Insertion Results + Action Taken: Insert New CIDR, Added CIDRs: [123.123.123.0/24], Removed CIDRs: [] + + Detect Super CIDR conflict |New CIDR 123.123.122.0/23 conflicted with [123.123.123.0/24 ] + Action Taken: Remove Existing CIDR, Added CIDRs: [], Removed CIDRs: [123.123.123.0/24] + Action Taken: Insert New CIDR, Added CIDRs: [123.123.122.0/23], Removed CIDRs: [] + + Detect Sub CIDR conflict |New CIDR 123.123.123.0/30 conflicted with [123.123.122.0/23 ] + Action Taken: Insert New CIDR, Added CIDRs: [123.123.123.0/30], Removed CIDRs: [] + Action Taken: Split Existing CIDR, Added CIDRs: [123.123.123.4/30 123.123.123.8/29 123.123.123.16/28 123.123.123.32/27 123.123.123.64/26 123.123.123.128/25 123.123.122.0/24], Removed CIDRs: [] + Action Taken: Remove Existing CIDR, Added CIDRs: [], Removed CIDRs: [123.123.122.0/23] + +## Internal CIDRs State + CIDR: 123.123.122.0/24, name: could be my home network + CIDR: 123.123.123.0/30, name: it is my home network + CIDR: 123.123.123.4/30, name: could be my home network + CIDR: 123.123.123.8/29, name: could be my home network + CIDR: 123.123.123.16/28, name: could be my home network + CIDR: 123.123.123.32/27, name: could be my home network + CIDR: 123.123.123.64/26, name: could be my home network + CIDR: 123.123.123.128/25, name: could be my home network + +## IP Lookup + found: CIDR: 123.123.123.16/28, name: could be my home network +``` + ### Running Tests To run tests for the supernet package, use the Go tool: ```sh -go test github.com/khalid_nowaf/supernet +go test github.com/khalid-nowaf/supernet ``` ### Contributing diff --git a/examples/simple.go b/examples/simple.go new file mode 100644 index 0000000..3f8217a --- /dev/null +++ b/examples/simple.go @@ -0,0 +1,77 @@ +package main + +import ( + "fmt" + "net" + + "github.com/khalid-nowaf/supernet/pkg/supernet" +) + +type Network struct { + cidr string + name string + priorities []uint8 +} + +func main() { + + // create random Cidrs + networks := []*Network{ + {cidr: "123.123.123.0/24", name: "maybe my home network", priorities: []uint8{0, 0, 1}}, + {cidr: "123.123.123.0/23", name: "could be my home network", priorities: []uint8{0, 0, 2}}, + {cidr: "123.123.123.0/30", name: "it is my home network", priorities: []uint8{0, 0, 3}}, + } + + super := supernet.NewSupernet() + for _, network := range networks { + if _, ipnet, err := net.ParseCIDR(network.cidr); err == nil { + // + metadata := supernet.NewMetadata(ipnet) + // attributes is used to store any additional data about the network + metadata.Attributes["name"] = network.name + // optional, it will be used to resolve conflict + // if no priority add. the size of the network e.g /32, will be used as priority + // so it is grunted smaller network will be not be over taken by larger network + metadata.Priority = network.priorities + // result has information about the conflict and how it solve it + insertResult := super.InsertCidr(ipnet, metadata) + fmt.Println(insertResult.String()) // see what happened + } + } + // get all networks (it will return conflict free networks) + nodes := super.AllCIDRS(false) + + for _, node := range nodes { + fmt.Printf("CIDR: %s, name: %s\n", supernet.NodeToCidr(node), node.Metadata().Attributes["name"]) + } + + // if you want to to lookup a an IP + ipnet, node, _ := super.LookupIP("123.123.123.16") + if ipnet != nil { + fmt.Printf("found: CIDR: %s, name: %s\n", ipnet.String(), node.Metadata().Attributes["name"]) + } + +} + +// OUTPUT: +// Action Taken: Insert New CIDR, Added CIDRs: [123.123.123.0/24], Removed CIDRs: [] + +// Detect Super CIDR conflict |New CIDR 123.123.122.0/23 conflicted with [123.123.123.0/24 ] +// Action Taken: Remove Existing CIDR, Added CIDRs: [], Removed CIDRs: [123.123.123.0/24] +// Action Taken: Insert New CIDR, Added CIDRs: [123.123.122.0/23], Removed CIDRs: [] + +// Detect Sub CIDR conflict |New CIDR 123.123.123.0/30 conflicted with [123.123.122.0/23 ] +// Action Taken: Insert New CIDR, Added CIDRs: [123.123.123.0/30], Removed CIDRs: [] +// Action Taken: Split Existing CIDR, Added CIDRs: [123.123.123.4/30 123.123.123.8/29 123.123.123.16/28 123.123.123.32/27 123.123.123.64/26 123.123.123.128/25 123.123.122.0/24], Removed CIDRs: [] +// Action Taken: Remove Existing CIDR, Added CIDRs: [], Removed CIDRs: [123.123.122.0/23] + +// CIDR: 123.123.122.0/24, name: could be my home network +// CIDR: 123.123.123.0/30, name: it is my home network +// CIDR: 123.123.123.4/30, name: could be my home network +// CIDR: 123.123.123.8/29, name: could be my home network +// CIDR: 123.123.123.16/28, name: could be my home network +// CIDR: 123.123.123.32/27, name: could be my home network +// CIDR: 123.123.123.64/26, name: could be my home network +// CIDR: 123.123.123.128/25, name: could be my home network + +// found: CIDR: 123.123.123.16/28, name: could be my home network diff --git a/pkg/supernet/supernet.go b/pkg/supernet/supernet.go index c2327cf..3795a45 100644 --- a/pkg/supernet/supernet.go +++ b/pkg/supernet/supernet.go @@ -27,6 +27,7 @@ func NewMetadata(ipnet *net.IPNet) *Metadata { return &Metadata{ originCIDR: ipnet, IsV6: isV6, + Attributes: map[string]string{}, } } @@ -80,7 +81,7 @@ func (super *Supernet) InsertCidr(ipnet *net.IPNet, metadata *Metadata) *Inserti } // LookupIP searches for the closest matching CIDR for a given IP address within the supernet. -func (super *Supernet) LookupIP(ip string) (*net.IPNet, error) { +func (super *Supernet) LookupIP(ip string) (*net.IPNet, *CidrTrie, error) { // Determine if the IP is IPv4 or IPv6 based on the presence of a colon. isV6 := strings.Contains(ip, ":") mask := 32 @@ -94,7 +95,7 @@ func (super *Supernet) LookupIP(ip string) (*net.IPNet, error) { // Parse the IP address with a full netmask to form a valid CIDR for bit conversion. _, parsedIP, err := net.ParseCIDR(fmt.Sprintf("%s/%d", ip, mask)) if err != nil { - return nil, err + return nil, nil, err } ipBits, _ := CidrToBits(parsedIP) @@ -103,9 +104,9 @@ func (super *Supernet) LookupIP(ip string) (*net.IPNet, error) { for i, bit := range ipBits { if supernet == nil { // Return nil if no matching CIDR is found in the trie. - return nil, nil + return nil, nil, nil } else if supernet.IsLeaf() { - return BitsToCidr(ipBits[:i], isV6), nil + return BitsToCidr(ipBits[:i], isV6), supernet, nil } else { supernet = supernet.Child(bit) } diff --git a/pkg/supernet/supernet_test.go b/pkg/supernet/supernet_test.go index 3a95b70..066377d 100644 --- a/pkg/supernet/supernet_test.go +++ b/pkg/supernet/supernet_test.go @@ -275,7 +275,7 @@ func TestLookIPv4(t *testing.T) { root.InsertCidr(sub, &Metadata{Priority: []uint8{1}, Attributes: makeCidrAtrr(sub.String())}) root.InsertCidr(super, &Metadata{Priority: []uint8{0}, Attributes: makeCidrAtrr(super.String())}) - cidr, err := root.LookupIP("192.168.25.154") + cidr, _, err := root.LookupIP("192.168.25.154") assert.NoError(t, err) assert.NotNil(t, cidr) @@ -290,19 +290,19 @@ func TestLookIPv6(t *testing.T) { root.InsertCidr(sub, &Metadata{Priority: []uint8{1}, Attributes: makeCidrAtrr(sub.String())}) root.InsertCidr(super, &Metadata{Priority: []uint8{0}, Attributes: makeCidrAtrr(super.String())}) - cidr, err := root.LookupIP("2001:0db8:abcd:12:1234::") + cidr, _, err := root.LookupIP("2001:0db8:abcd:12:1234::") assert.NoError(t, err) assert.NotNil(t, cidr) assert.Equal(t, "2001:db8:abcd:12:1234::/80", cidr.String()) - cidr, err = root.LookupIP("2001:db8:abcd:12:1234::abcd") + cidr, _, err = root.LookupIP("2001:db8:abcd:12:1234::abcd") assert.NoError(t, err) assert.NotNil(t, cidr) assert.Equal(t, "2001:db8:abcd:12:1234::/80", cidr.String()) - cidr, err = root.LookupIP("2001:db8:abcd:12:0000::1") + cidr, _, err = root.LookupIP("2001:db8:abcd:12:0000::1") assert.NoError(t, err) assert.NotNil(t, cidr) diff --git a/pkg/supernet/uitls.go b/pkg/supernet/uitls.go index e53d906..745f101 100644 --- a/pkg/supernet/uitls.go +++ b/pkg/supernet/uitls.go @@ -57,18 +57,6 @@ func BitsToCidr(bits []int, ipV6 bool) *net.IPNet { // NodeToCidr converts a given trie node into a CIDR (Classless Inter-Domain Routing) string representation. // This function uses the node's path to generate the CIDR string. -// -// Parameters: -// - t: Pointer to a trie.BinaryTrie node of type Metadata. It must contain valid metadata and a path. -// - isV6: A boolean indicating whether the IP version is IPv6. True means IPv6, false means IPv4. -// -// Returns: -// - A string representing the CIDR notation of the node's IP address. -// -// Panics: -// - If the node's metadata is nil, indicating that it is a path node without associated CIDR data, -// this function will panic with a specific error message. -// // Example: // // Given a trie node representing an IP address with metadata, this function will output the address in CIDR format,