Skip to content

Commit

Permalink
Ref #499 - Add support for integer AS in ROA file
Browse files Browse the repository at this point in the history
(cherry picked from commit a7a63a8)
  • Loading branch information
mxsasha committed Jun 18, 2021
1 parent 44d922f commit 237c600
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 26 deletions.
14 changes: 7 additions & 7 deletions irrd/rpki/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ class ROADataImporter:
def __init__(self, rpki_json_str: str, slurm_json_str: Optional[str],
database_handler: DatabaseHandler):
self.roa_objs: List[ROA] = []
self._filtered_asns: Set[str] = set()
self._filtered_asns: Set[int] = set()
self._filtered_prefixes: IPSet = IPSet()
self._filtered_combined: Dict[str, IPSet] = defaultdict(IPSet)
self._filtered_combined: Dict[int, IPSet] = defaultdict(IPSet)

self._load_roa_dicts(rpki_json_str)
if slurm_json_str:
Expand All @@ -51,7 +51,7 @@ def __init__(self, rpki_json_str: str, slurm_json_str: Optional[str],

for roa_dict in self._roa_dicts:
try:
asn = roa_dict['asn']
_, asn = parse_as_number(roa_dict['asn'], permit_plain=True)
prefix = IP(roa_dict['prefix'])
ta = roa_dict['ta']
if ta != SLURM_TRUST_ANCHOR:
Expand Down Expand Up @@ -116,11 +116,11 @@ def _load_slurm(self, slurm_json_str: str):
filters = slurm.get('validationOutputFilters', {}).get('prefixFilters', [])
for item in filters:
if 'asn' in item and 'prefix' not in item:
self._filtered_asns.add('AS' + str(item['asn']))
self._filtered_asns.add(int(item['asn']))
if 'asn' not in item and 'prefix' in item:
self._filtered_prefixes.add(IP(item['prefix']))
if 'asn' in item and 'prefix' in item:
self._filtered_combined['AS' + str(item['asn'])].add(IP(item['prefix']))
self._filtered_combined[int(item['asn'])].add(IP(item['prefix']))

assertions = slurm.get('locallyAddedAssertions', {}).get('prefixAssertions', [])
for assertion in assertions:
Expand All @@ -142,11 +142,11 @@ class ROA:
This is used when (re-)importing all ROAs, to save the data to the DB,
and by the BulkRouteROAValidator when validating all existing routes.
"""
def __init__(self, prefix: IP, asn: str, max_length: str, trust_anchor: str):
def __init__(self, prefix: IP, asn: int, max_length: str, trust_anchor: str):
try:
self.prefix = prefix
self.prefix_str = str(prefix)
_, self.asn = parse_as_number(asn)
self.asn = asn
self.max_length = int(max_length)
self.trust_anchor = trust_anchor
except ValueError as ve:
Expand Down
16 changes: 14 additions & 2 deletions irrd/rpki/tests/test_importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def test_valid_process(self, monkeypatch, mock_scopefilter):

rpki_data = ujson.dumps({
"roas": [{
"asn": "AS64496",
"asn": "64496",
"prefix": "192.0.2.0/24",
"maxLength": 26,
"ta": "APNIC RPKI Root"
Expand All @@ -40,7 +40,7 @@ def test_valid_process(self, monkeypatch, mock_scopefilter):
"ta": "RIPE NCC RPKI Root"
}, {
# Filtered out by SLURM due to origin
"asn": "AS64498",
"asn": "64498",
"prefix": "192.0.2.0/24",
"maxLength": 32,
"ta": "APNIC RPKI Root"
Expand Down Expand Up @@ -219,6 +219,18 @@ def test_invalid_data_in_roa(self, monkeypatch, mock_scopefilter):
ROADataImporter(data, None, mock_dh)
assert 'Invalid ROA: prefix size 24 is smaller than max length 22' in str(rpe.value)

data = ujson.dumps({
"roas": [{
"asn": "AS64496",
"prefix": "192.0.2.0/24",
"maxLength": 'xx',
"ta": "APNIC RPKI Root"
}]
})
with pytest.raises(ROAParserException) as rpe:
ROADataImporter(data, None, mock_dh)
assert 'xx' in str(rpe.value)

assert flatten_mock_calls(mock_dh) == []

def test_invalid_slurm_version(self, monkeypatch, mock_scopefilter):
Expand Down
14 changes: 7 additions & 7 deletions irrd/rpki/tests/test_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,20 +124,20 @@ def test_validate_routes_from_roa_objs(self, monkeypatch, config_override):
roas = [
# Valid for pk_route_v4_d0_l25 and pk_route_v4_d0_l24
# - the others have incorrect origin or are too small.
ROA(IP('192.0.2.0/24'), 'AS65546', '28', 'TEST TA'),
ROA(IP('192.0.2.0/24'), 65546, '28', 'TEST TA'),
# Matches the origin of pk_route_v4_d128_l25,
# but not max_length.
ROA(IP('192.0.2.0/24'), 'AS65547', '24', 'TEST TA'),
ROA(IP('192.0.2.0/24'), 65547, '24', 'TEST TA'),
# Matches pk_route_v6, but not max_length.
ROA(IP('2001:db8::/30'), 'AS65547', '30', 'TEST TA'),
ROA(IP('2001:db8::/30'), 65547, '30', 'TEST TA'),
# Matches pk_route_v6, but not on origin.
ROA(IP('2001:db8::/32'), 'AS65548', '32', 'TEST TA'),
ROA(IP('2001:db8::/32'), 65548, '32', 'TEST TA'),
# Matches pk_route_v6
ROA(IP('2001:db8::/32'), 'AS65547', '64', 'TEST TA'),
ROA(IP('2001:db8::/32'), 65547, '64', 'TEST TA'),
# Matches no routes, no effect
ROA(IP('203.0.113.0/32'), 'AS65547', '32', 'TEST TA'),
ROA(IP('203.0.113.0/32'), 65547, '32', 'TEST TA'),
# AS0 can not match
ROA(IP('203.0.113.1/32'), 'AS0', '32', 'TEST TA'),
ROA(IP('203.0.113.1/32'), 0, '32', 'TEST TA'),
]
result = BulkRouteROAValidator(mock_dh, roas).validate_all_routes(sources=['TEST1'])
new_valid_objs, new_invalid_objs, new_unknown_objs = result
Expand Down
5 changes: 4 additions & 1 deletion irrd/utils/tests/test_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@


def test_validate_as_number():
assert parse_as_number('AS00') == ('AS0', 0)
assert parse_as_number('AS012345') == ('AS12345', 12345)
assert parse_as_number('as4294967290') == ('AS4294967290', 4294967290)
assert parse_as_number('012345', permit_plain=True) == ('AS12345', 12345)
assert parse_as_number(12345, permit_plain=True) == ('AS12345', 12345)

with raises(ValidationError) as ve:
parse_as_number('12345')
Expand All @@ -17,4 +20,4 @@ def test_validate_as_number():

with raises(ValidationError) as ve:
parse_as_number('AS429496729999')
assert 'maximum value is' in str(ve.value)
assert 'valid range is' in str(ve.value)
24 changes: 15 additions & 9 deletions irrd/utils/validators.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
from typing import Tuple


def parse_as_number(value: str) -> Tuple[str, int]:
def parse_as_number(value: Union[str, int], permit_plain=False) -> Tuple[str, int]:
"""Validate and clean an AS number. Returns it in ASxxxx and numeric format."""
value = value.upper()
if not value.startswith('AS'):
raise ValidationError(f'Invalid AS number {value}: must start with "AS"')
if isinstance(value, str):
value = value.upper()
if not permit_plain and not value.startswith('AS'):
raise ValidationError(f'Invalid AS number {value}: must start with "AS"')

if not value[2:].isnumeric():
raise ValidationError(f'Invalid AS number {value}: number part is not numeric')
start_index = 2 if value.startswith('AS') else 0

value_int = int(value[2:])
if value_int > 4294967295:
raise ValidationError(f'Invalid AS number {value}: maximum value is 4294967295')
if not value[start_index:].isnumeric():
raise ValidationError(f'Invalid AS number {value}: number part is not numeric')

value_int = int(value[start_index:])
else:
value_int = value

if not 0 <= value_int < 4294967295:
raise ValidationError(f'Invalid AS number {value}: valid range is 0-4294967295')

return 'AS' + str(value_int), value_int

Expand Down

0 comments on commit 237c600

Please sign in to comment.