Stacktrace generation reverse IPv6 DNS zone with hex characters in begin of PTR
Steps to reproduce
-
The generation of an IPv6 DNS Reverse Zone fails in occasions where non-digit characters are compared during sort of the zonefile.
-
This generates a stack trace.
-
Easily reproducible. Certainly when using non-digit hexadecimal characters at the end of an IPv6 address.
What is the current bug behavior?
Python stack trace when the generation of the IPv6 RR zone fails. No update of the reverse zone in the DNS-zones view.
What is the expected correct behavior?
A correct and updated IPv6 reverse DNS zone.
Relevant logs and/or screenshots
2021-05-12 10:05:02,592 [noc.core.debug] UNHANDLED EXCEPTION (2021-05-12 10:05:02.557324)
PROCESS: ./commands/datastream.py
VERSION: 20.4.3
BRANCH: HEAD CHANGESET: c86766dc
ERROR FINGERPRINT: 242fa573-9da4-5260-bc1f-a75563224ff5
WORKING DIRECTORY: /opt/noc
EXCEPTION: <class 'TypeError'> '<' not supported between instances of 'int' and 'str'
START OF TRACEBACK
------------------------------------------------------------------------
File: core/dns/rr.py (Line: 62)
Function: __lt__
55 # Check type preferences
56 p1 = TYPE_PREF.get(self.type, DEFAULT_PREF)
57 p2 = TYPE_PREF.get(other.type, DEFAULT_PREF)
58 if p1 != p2:
59 return p1 < p2
60 # Compare PTR
61 if self.type == "PTR" and other.type == "PTR":
62 ==> return self._order < other._order
63 # Compare by name
64 if self._sorder < other._sorder:
65 return True
66 elif self._sorder > other._sorder:
67 return False
68 # Compare by type
Variables:
self =
<RR 0.6.1.0.0.0.0.0.0.0.0.0.0.0.0.0.2.0.0.0.0.0 PTR ae21.bkl001a-jnx-01.bugreport.net.>
other =
<RR f.3.1.0.0.0.0.0.0.0.0.0.0.0.0.0.2.0.0.0.0.0 PTR ae25.bd011a-jnx-01.bugreport.net.>
p1 = 100
p2 = 100
------------------------------------------------------------------------
File: services/datastream/streams/dnszone.py (Line: 60)
Function: get_records
53 """
54 zone_iters = [cls.iter_soa(zone)]
55 if zone.type == ZONE_FORWARD:
56 zone_iters += [sorted(cls.iter_forward(zone))]
57 elif zone.type == ZONE_REVERSE_IPV4:
58 zone_iters += [sorted(cls.iter_reverse_ipv4(zone))]
59 elif zone.type == ZONE_REVERSE_IPV6:
60 ==> zone_iters += [sorted(cls.iter_reverse_ipv6(zone))]
61 return [x.to_json() for x in chain(*tuple(zone_iters))]
62
63 @classmethod
64 def iter_soa(cls, zone):
65 """
66 Yield SOA record
Variables:
cls = <class 'noc.services.datastream.streams.dnszone.DNSZoneDataStream'>
zone = <DNSZone: e.0.0.1.6.0.1.0.0.2.ip6.arpa>
zone_iters = [<generator object DNSZoneDataStream.iter_soa at 0x7f5d04d96c80>]
------------------------------------------------------------------------
File: services/datastream/streams/dnszone.py (Line: 44)
Function: get_object
37 zone = zone[0]
38 return {
39 "id": str(zone.id),
40 "name": str(zone.name),
41 "serial": str(zone.serial),
42 "masters": [str(x[:-1]) for x in zone.masters],
43 "slaves": [str(x[:-1]) for x in zone.slaves],
44 ==> "records": cls.get_records(zone),
45 }
46
47 @classmethod
48 def get_records(cls, zone):
49 """
50 Get zone records
Variables:
cls = <class 'noc.services.datastream.streams.dnszone.DNSZoneDataStream'>
id = 45
zone = <DNSZone: e.0.0.1.6.0.1.0.0.2.ip6.arpa>
------------------------------------------------------------------------
File: core/datastream/base.py (Line: 234)
Function: _get_current_data
227 @classmethod
228 def _get_current_data(
229 cls, obj_id, delete=False
230 ) -> Tuple[Dict[str, Any], Optional[Dict[str, Any]]]:
231 if delete:
232 return cls.get_deleted_object(obj_id), None
233 try:
234 ==> data = cls.get_object(obj_id)
235 meta = cls.get_meta(data)
236 return data, meta
237 except KeyError:
238 return cls.get_deleted_object(obj_id), None
239
240 @classmethod
Variables:
cls = <class 'noc.services.datastream.streams.dnszone.DNSZoneDataStream'>
obj_id = 45
delete = False
------------------------------------------------------------------------
File: core/datastream/base.py (Line: 149)
Function: bulk_update
142 for doc in coll.find({cls.F_ID: {"$in": chunk}}, {cls.F_ID: 1, cls.F_HASH: 1})
143 }
144 bulk = []
145 fmt_data = defaultdict(list)
146 fmt_bulk = {}
147 # Apply default format
148 for obj_id in chunk:
149 ==> data, meta = cls._get_current_data(obj_id)
150 cls._update_object(data=data, meta=meta, state=current_state, bulk=bulk)
151 # Process formats
152 for fmt in fmt_handler:
153 fmt_data[fmt] += list(fmt_handler[fmt](data))
154 # Apply formats
155 for fmt in fmt_data:
Possible fixes
Problem appears to be in core/dns/rr.py and more specifically;
if self.type == "PTR" and other.type == "PTR":
return self._order < other._order
Here the tuples of ._order are compared from left to right. However earlier on the _order type was casted int when it was an integer, but stays string for non-integers.
if type == "PTR":
self._order = tuple(self.maybe_int(x) for x in name.split("."))
This imo causes the stacktrace above when the comparision first meets a hexadecimal character instead of a digit to be compared.
I tried this possible fix to ensure this was the problem;
def maybe_int(v):
try:
return int(v)
except ValueError:
return int(v,base=16)
Here a base16 int is returned instead of 'v' as string. Doing so fixes the problem in our situation. If this is safe enough for all environments I cannot decide. But since this is only for reverse zones, it appears safe to me. Otherwise the code needs if-then-else based on the zone-type.
./noc about
)
Paste NOC version (NOC version is: 20.4.3