Skip to content

Commit 82fabc6

Browse files
authored
Merge pull request #3 from vimt/feature/insert-subnet
feat: insert subnet
2 parents edd4790 + c3b12ff commit 82fabc6

File tree

3 files changed

+100
-17
lines changed

3 files changed

+100
-17
lines changed

mmdb_writer.py

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
# coding: utf-8
2-
__version__ = '0.1.0'
2+
__version__ = '0.1.1'
33

44
import logging
55
import math
66
import struct
77
import time
88
from typing import Union
99

10-
from netaddr import IPSet
10+
from netaddr import IPSet, IPNetwork
1111

1212
MMDBType = Union[dict, list, str, bytes, int, bool]
1313

@@ -426,18 +426,24 @@ def insert_network(self, network: IPSet, content: MMDBType):
426426
cidr = cidr.ipv6(True)
427427
node = self.tree
428428
bits = list(bits_rstrip(cidr.value, self._bit_length, cidr.prefixlen))
429-
try:
430-
for i in bits[:-1]:
431-
node = node.get_or_create(i)
432-
if node[bits[-1]] is not None:
433-
logger.warning("address %s info is not empty: %s, will override with %s",
434-
cidr, node[bits[-1]], leaf)
435-
except (AttributeError, TypeError) as e:
436-
bits_str = ''.join(map(str, bits))
437-
logger.warning("{cidr}({bits_str})[{content}] is subnet of {node}, pass!"
438-
.format(cidr=cidr, bits_str=bits_str, content=content, node=node))
439-
continue
440-
node[bits[-1]] = leaf
429+
current_node = node
430+
supernet_leaf = None # Tracks whether we are inserting into a subnet
431+
for (index, ip_bit) in enumerate(bits[:-1]):
432+
previous_node = current_node
433+
current_node = previous_node.get_or_create(ip_bit)
434+
435+
if isinstance(current_node, SearchTreeLeaf):
436+
current_cidr = IPNetwork((int(''.join(map(str, bits[:index + 1])).ljust(self._bit_length, '0'), 2), index + 1))
437+
logger.info(f"Inserting {cidr} ({content}) into subnet of {current_cidr} ({current_node.value})")
438+
supernet_leaf = current_node
439+
current_node = SearchTreeNode()
440+
previous_node[ip_bit] = current_node
441+
442+
if supernet_leaf:
443+
next_bit = bits[index + 1]
444+
# Insert supernet information on each inverse bit of the current subnet
445+
current_node[1 - next_bit] = supernet_leaf
446+
current_node[bits[-1]] = leaf
441447

442448
def to_db_file(self, filename: str):
443449
return TreeWriter(self.tree, self._build_meta()).write(filename)

tests/__init__.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +0,0 @@
1-
# coding: utf-8
2-
3-
# TODO: add tests

tests/test.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# coding: utf-8
2+
import logging
3+
import os.path
4+
import unittest
5+
6+
import maxminddb
7+
from netaddr import IPSet
8+
9+
from mmdb_writer import MMDBWriter
10+
11+
logging.basicConfig(format="[%(asctime)s: %(levelname)s] %(message)s", level=logging.INFO)
12+
info1 = {'country': 'c1', 'isp': 'ISP1'}
13+
info2 = {'country': 'c2', 'isp': 'ISP2'}
14+
15+
16+
class TestBuild(unittest.TestCase):
17+
def setUp(self) -> None:
18+
self.filename = '_test.mmdb'
19+
20+
def tearDown(self) -> None:
21+
if os.path.exists(self.filename):
22+
os.remove(self.filename)
23+
24+
def test_metadata(self):
25+
ip_version = 6
26+
database_type = 'test_database_type'
27+
languages = ['en', 'ch']
28+
description = {'en': 'en test', 'ch': 'ch test'}
29+
writer = MMDBWriter(ip_version=ip_version, database_type=database_type,
30+
languages=languages, description=description,
31+
ipv4_compatible=False)
32+
writer.to_db_file(self.filename)
33+
for mode in (maxminddb.MODE_MMAP_EXT, maxminddb.MODE_MMAP, maxminddb.MODE_FILE):
34+
m = maxminddb.open_database(self.filename, mode=mode)
35+
self.assertEqual(ip_version, m.metadata().ip_version, mode)
36+
self.assertEqual(database_type, m.metadata().database_type, mode)
37+
self.assertEqual(languages, m.metadata().languages, mode)
38+
self.assertEqual(description, m.metadata().description, mode)
39+
m.close()
40+
41+
def test_encode_type(self):
42+
writer = MMDBWriter()
43+
info = {'int': 1, 'float': 1.0 / 3, 'list': ['a', 'b', 'c'], 'dict': {'k': 'v'}, 'bytes': b'bytes', 'str': 'str'}
44+
writer.insert_network(IPSet(['1.1.0.0/24']), info)
45+
writer.to_db_file(self.filename)
46+
for mode in (maxminddb.MODE_MMAP_EXT, maxminddb.MODE_MMAP, maxminddb.MODE_FILE):
47+
m = maxminddb.open_database(self.filename, mode=mode)
48+
get = m.get('1.1.0.255')
49+
self.assertEqual(len(info), len(get), mode)
50+
self.assertEqual(info['int'], get['int'], mode)
51+
self.assertTrue(abs(info['float'] - get['float']) < 1e-5, mode)
52+
self.assertEqual(info['list'], get['list'], mode)
53+
self.assertEqual(info['dict'], get['dict'], mode)
54+
self.assertEqual(info['bytes'], get['bytes'], mode)
55+
self.assertEqual(info['str'], get['str'], mode)
56+
m.close()
57+
58+
def test_4in6(self):
59+
writer = MMDBWriter(ip_version=6, ipv4_compatible=True)
60+
writer.insert_network(IPSet(['1.1.0.0/24']), info1)
61+
writer.insert_network(IPSet(['fe80::/16']), info2)
62+
writer.to_db_file(self.filename)
63+
for mode in (maxminddb.MODE_MMAP_EXT, maxminddb.MODE_MMAP, maxminddb.MODE_FILE):
64+
m = maxminddb.open_database(self.filename, mode=mode)
65+
self.assertEqual(info1, m.get('1.1.0.1'), mode)
66+
self.assertEqual(info2, m.get('fe80::1'), mode)
67+
m.close()
68+
69+
def test_insert_subnet(self):
70+
writer = MMDBWriter()
71+
writer.insert_network(IPSet(['1.0.0.0/8']), info1)
72+
writer.insert_network(IPSet(['1.10.10.0/24']), info2)
73+
writer.to_db_file(self.filename)
74+
for mode in (maxminddb.MODE_MMAP_EXT, maxminddb.MODE_MMAP, maxminddb.MODE_FILE):
75+
m = maxminddb.open_database(self.filename, mode=mode)
76+
self.assertEqual(info1, m.get('1.1.0.1'), mode)
77+
self.assertEqual(info1, m.get('1.10.0.1'), mode)
78+
self.assertEqual(info2, m.get('1.10.10.1'), mode)
79+
m.close()
80+

0 commit comments

Comments
 (0)