Skip to content

Commit

Permalink
Fix #192 - Resolve as(-sets) contained in route-sets (#204)
Browse files Browse the repository at this point in the history
  • Loading branch information
mxsasha authored Feb 18, 2019
1 parent 42969a4 commit 9b72e23
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 10 deletions.
2 changes: 2 additions & 0 deletions docs/users/queries.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ IRRd style queries
(and possibly names of other sets, if the search was not recursive),
separated by spaces. For example:
``!iRS-EXAMPLE,1`` returns all members of `RS-EXAMPLE`, recursively.
If a `route-set` has `as-sets` or AS number as members, the response includes
the prefixes originating from that AS, or the ASes in that set.
If the ``compatibility.ipv4_only_route_set_members`` setting is enabled,
IPv6 prefixes will not be returned.
* ``!j`` returns the serial range for each source, along with the most
Expand Down
25 changes: 17 additions & 8 deletions irrd/server/whois/query_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ def _recursive_set_resolve(self, members: Set[str], sets_seen=None) -> Set[str]:
sets_seen.update(members)

set_members = set()
resolved_as_members = set()
sub_members, leaf_members = self._find_set_members(members)

for sub_member in sub_members:
Expand All @@ -282,15 +283,21 @@ def _recursive_set_resolve(self, members: Set[str], sets_seen=None) -> Set[str]:
continue
except ValueError:
pass
if self._current_set_root_object_class is None or self._current_set_root_object_class == 'as-set':
try:
parse_as_number(sub_member)
# AS numbers are permitted in route-sets and as-sets, per RFC 2622 5.3.
# When an AS number is encountered as part of route-set resolving,
# the prefixes originating from that AS should be added to the response.
try:
as_number_formatted, _ = parse_as_number(sub_member)
if self._current_set_root_object_class == 'route-set':
set_members.update(self.preloader.routes_for_origins([as_number_formatted]))
resolved_as_members.add(sub_member)
else:
set_members.add(sub_member)
continue
except ValueError:
pass
continue
except ValueError:
pass

further_resolving_required = sub_members - set_members - sets_seen
further_resolving_required = sub_members - set_members - sets_seen - resolved_as_members
new_members = self._recursive_set_resolve(further_resolving_required, sets_seen)
set_members.update(new_members)

Expand All @@ -316,7 +323,9 @@ def _find_set_members(self, set_names: Set[str]) -> Tuple[Set[str], Set[str]]:
query = self._prepare_query(column_names=columns)

object_classes = ['as-set', 'route-set']
if self._current_set_root_object_class:
# Per RFC 2622 5.3, route-sets can refer to as-sets,
# but as-sets can only refer to other as-sets.
if self._current_set_root_object_class == 'as-set':
object_classes = [self._current_set_root_object_class]

query = query.object_classes(object_classes).rpsl_pks(set_names)
Expand Down
53 changes: 51 additions & 2 deletions irrd/server/whois/tests/test_query_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -595,7 +595,7 @@ def test_handle_irrd_routes_for_as_set(self, prepare_parser, monkeypatch):

assert not mock_dq.mock_calls

def test_as_route_set_members(self, prepare_parser):
def test_as_set_members(self, prepare_parser):
mock_dq, mock_dh, mock_preloader, parser = prepare_parser

mock_query_result1 = [
Expand Down Expand Up @@ -656,7 +656,6 @@ def test_as_route_set_members(self, prepare_parser):
assert response.response_type == WhoisQueryResponseType.SUCCESS
assert response.mode == WhoisQueryResponseMode.IRRD
assert response.result == 'AS65544 AS65545 AS65547'
print(flatten_mock_calls(mock_dq))
assert flatten_mock_calls(mock_dq) == [
['object_classes', (['as-set', 'route-set'],), {}],
['rpsl_pks', ({'AS-FIRSTLEVEL'},), {}],
Expand All @@ -679,6 +678,56 @@ def test_as_route_set_members(self, prepare_parser):
['rpsl_pks', ({'AS-NOTEXIST'},), {}]
]

def test_route_set_members(self, prepare_parser):
mock_dq, mock_dh, mock_preloader, parser = prepare_parser

mock_query_result1 = [
{
'pk': uuid.uuid4(),
'rpsl_pk': 'RS-FIRSTLEVEL',
'parsed_data': {'as-set': 'RS-FIRSTLEVEL',
'members': ['RS-SECONDLEVEL', 'RS-2nd-UNKNOWN']},
'object_text': 'text',
'object_class': 'route-set',
'source': 'TEST1',
},
]
mock_query_result2 = [
{
'pk': uuid.uuid4(),
'rpsl_pk': 'RS-SECONDLEVEL',
'parsed_data': {'as-set': 'RS-SECONDLEVEL', 'members': ['AS-REFERRED', '192.0.2.0/25']},
'object_text': 'text',
'object_class': 'route-set',
'source': 'TEST1',
},
]
mock_query_result3 = [
{
'pk': uuid.uuid4(),
'rpsl_pk': 'AS-REFERRED',
'parsed_data': {'as-set': 'AS-REFERRED',
'members': ['AS65545']},
'object_text': 'text',
'object_class': 'as-set',
'source': 'TEST2',
},
]
mock_query_iterator = iter([mock_query_result1, mock_query_result2, mock_query_result3, []])
mock_dh.execute_query = lambda query: iter(next(mock_query_iterator))
mock_preloader.routes_for_origins = Mock(return_value=['192.0.2.128/25'])

response = parser.handle_query('!iRS-FIRSTLEVEL,1')
assert response.response_type == WhoisQueryResponseType.SUCCESS
assert response.mode == WhoisQueryResponseMode.IRRD
assert response.result == '192.0.2.0/25 192.0.2.128/25'
assert flatten_mock_calls(mock_dq) == [
['object_classes', (['as-set', 'route-set'],), {}], ['rpsl_pks', ({'RS-FIRSTLEVEL'},), {}],
['object_classes', (['as-set', 'route-set'],), {}],
['rpsl_pks', ({'RS-SECONDLEVEL', 'RS-2nd-UNKNOWN'},), {}],
['object_classes', (['as-set', 'route-set'],), {}], ['rpsl_pks', ({'AS-REFERRED'},), {}],
]

def test_as_route_set_mbrs_by_ref(self, prepare_parser):
mock_dq, mock_dh, mock_preloader, parser = prepare_parser

Expand Down

0 comments on commit 9b72e23

Please sign in to comment.