Skip to content

Commit

Permalink
feat: add regex to as path access lists (#7250)
Browse files Browse the repository at this point in the history
Use a regular expression to match AS_PATH in access-list instead of converting a single integer to a regular expression.
  • Loading branch information
lukaskoenen committed Sep 20, 2021
1 parent 034913a commit 064c6a6
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ options {
tokens {
QUOTED_TEXT,
REMARK_TEXT,
WORD
WORD,
REGEX
}

ACCESS_LIST
:
'access-list' -> pushMode(M_Word)
'access-list' -> pushMode(M_AccessList)
;

ACTIVATE: 'activate';
Expand Down Expand Up @@ -799,6 +800,18 @@ F_Uint32
| '429496729' [0-5]
;

fragment
F_Regex
:
F_RegexChar+
;

fragment
F_RegexChar
:
[0-9_^|[,{}()\]$*+.?-]
;

fragment
F_Word
:
Expand Down Expand Up @@ -1104,3 +1117,34 @@ M_Update_NEWLINE
F_Newline+ -> type ( NEWLINE ), popMode
;

mode M_AccessList;

M_AccessList_DENY
:
'deny' -> type(DENY)
;

M_AccessList_PERMIT
:
'permit' -> type(PERMIT)
;

M_AsPathAccessList_REGEX
:
F_Regex -> type(REGEX)
;

M_AsPathAccessList_WORD
:
F_Word -> type(WORD)
;

M_AccessList_NEWLINE
:
F_Newline+ -> type (NEWLINE) , popMode
;

M_AccessList_WS
:
F_Whitespace+ -> channel (HIDDEN)
;
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ statement

ip_as_path
:
AS_PATH ACCESS_LIST name = word action = line_action asn = uint32 NEWLINE
AS_PATH ACCESS_LIST name = word action = access_list_action as_path_regex = REGEX NEWLINE
;

s_agentx
Expand Down Expand Up @@ -185,5 +185,3 @@ s_username
:
USERNAME null_rest_of_line
;


Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ ip_prefix_length
UINT8
;

access_list_action
:
PERMIT
| DENY
;

line_action
:
deny = DENY
Expand Down Expand Up @@ -153,4 +159,4 @@ word
null_rest_of_line
:
~NEWLINE* NEWLINE
;
;
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
import org.batfish.grammar.BatfishCombinedParser;
import org.batfish.grammar.SilentSyntaxListener;
import org.batfish.grammar.UnrecognizedLineToken;
import org.batfish.grammar.cumulus_frr.CumulusFrrParser.Access_list_actionContext;
import org.batfish.grammar.cumulus_frr.CumulusFrrParser.Agg_feature_as_setContext;
import org.batfish.grammar.cumulus_frr.CumulusFrrParser.Agg_feature_matching_med_onlyContext;
import org.batfish.grammar.cumulus_frr.CumulusFrrParser.Agg_feature_originContext;
Expand Down Expand Up @@ -1775,14 +1776,23 @@ public void exitIp_route(Ip_routeContext ctx) {
}
}

private LineAction toLineAction(Access_list_actionContext ctx) {
if (ctx.PERMIT() != null) {
return LineAction.PERMIT;
} else {
assert ctx.DENY() != null;
return LineAction.DENY;
}
}

@Override
public void exitIp_as_path(Ip_as_pathContext ctx) {
String name = ctx.name.getText();
LineAction action = ctx.action.permit != null ? LineAction.PERMIT : LineAction.DENY;
long asNum = toLong(ctx.asn);
LineAction action = toLineAction(ctx.action);
String regex = ctx.as_path_regex.getText();
_frr.getIpAsPathAccessLists()
.computeIfAbsent(name, IpAsPathAccessList::new)
.addLine(new IpAsPathAccessListLine(action, asNum));
.addLine(new IpAsPathAccessListLine(action, regex));
_c.defineStructure(IP_AS_PATH_ACCESS_LIST, name, ctx);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1559,7 +1559,7 @@ static void convertIpCommunityLists(
new CommunityMatchRegex(ColonSeparatedRendering.instance(), toJavaRegex(line.getRegex())));
}

private static @Nonnull String toJavaRegex(String cumulusRegex) {
static @Nonnull String toJavaRegex(String cumulusRegex) {
String withoutQuotes;
if (cumulusRegex.charAt(0) == '"' && cumulusRegex.charAt(cumulusRegex.length() - 1) == '"') {
withoutQuotes = cumulusRegex.substring(1, cumulusRegex.length() - 1);
Expand All @@ -1585,10 +1585,7 @@ static void convertIpAsPathAccessLists(
asPathAccessList.getLines().stream()
// TODO Check FRR AS path match semantics.
// This regex assumes we should match any path containing the specified ASN anywhere.
.map(
line ->
new AsPathAccessListLine(
line.getAction(), String.format("(^| )%s($| )", line.getAsNum())))
.map(line -> new AsPathAccessListLine(line.getAction(), line.getRegex()))
.collect(ImmutableList.toImmutableList());
return new AsPathAccessList(name, lines);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@
/** Represents one line of a Cumulus AS-path access list. */
public class IpAsPathAccessListLine implements Serializable {
private final @Nonnull LineAction _action;
private final long _asNum;
private final String _regex;

public IpAsPathAccessListLine(@Nonnull LineAction action, long asNum) {
public IpAsPathAccessListLine(@Nonnull LineAction action, String regex) {
_action = action;
_asNum = asNum;
_regex = regex;
}

@Nonnull
public LineAction getAction() {
return _action;
}

public long getAsNum() {
return _asNum;
public String getRegex() {
return _regex;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1447,31 +1447,62 @@ public void testCumulusFrrVrfRouteMapSetCommunity() {
@Test
public void testCumulusFrrIpAsPathAccessList() {
String name = "NAME";
long as1 = 11111;
long as2 = 22222;
String as1 = "^11111$";
String as2 = "_1_";
String as3 = "^1[1-2]";
String as4 = "^1(1)";
parse(
String.format(
"ip as-path access-list %s permit %s\n" + "ip as-path access-list %s deny %s\n",
name, as1, name, as2));
"ip as-path access-list %s permit %s\n"
+ "ip as-path access-list %s permit %s\n"
+ "ip as-path access-list %s permit %s\n"
+ "ip as-path access-list %s permit %s\n"
+ "ip as-path access-list %s deny %s\n"
+ "ip as-path access-list %s deny %s\n"
+ "ip as-path access-list %s deny %s\n"
+ "ip as-path access-list %s deny %s\n",
name, as1, name, as2, name, as3, name, as4, name, as1, name, as2, name, as3, name,
as4));

// Check that config has the expected AS-path access list with the expected name and num lines
assertThat(_frr.getIpAsPathAccessLists().keySet(), contains(name));
IpAsPathAccessList asPathAccessList = _frr.getIpAsPathAccessLists().get(name);
assertThat(asPathAccessList.getName(), equalTo(name));
assertThat(asPathAccessList.getLines(), hasSize(2));
assertThat(asPathAccessList.getLines(), hasSize(8));

// Check that lines look as expected
IpAsPathAccessListLine line0 = asPathAccessList.getLines().get(0);
IpAsPathAccessListLine line1 = asPathAccessList.getLines().get(1);
IpAsPathAccessListLine line2 = asPathAccessList.getLines().get(2);
IpAsPathAccessListLine line3 = asPathAccessList.getLines().get(3);
IpAsPathAccessListLine line4 = asPathAccessList.getLines().get(4);
IpAsPathAccessListLine line5 = asPathAccessList.getLines().get(5);
IpAsPathAccessListLine line6 = asPathAccessList.getLines().get(6);
IpAsPathAccessListLine line7 = asPathAccessList.getLines().get(7);

assertThat(line0.getAction(), equalTo(LineAction.PERMIT));
assertThat(line1.getAction(), equalTo(LineAction.DENY));
assertThat(line0.getAsNum(), equalTo(as1));
assertThat(line1.getAsNum(), equalTo(as2));
assertThat(line1.getAction(), equalTo(LineAction.PERMIT));
assertThat(line2.getAction(), equalTo(LineAction.PERMIT));
assertThat(line3.getAction(), equalTo(LineAction.PERMIT));
assertThat(line4.getAction(), equalTo(LineAction.DENY));
assertThat(line5.getAction(), equalTo(LineAction.DENY));
assertThat(line6.getAction(), equalTo(LineAction.DENY));
assertThat(line7.getAction(), equalTo(LineAction.DENY));

assertThat(line0.getRegex(), equalTo(as1));
assertThat(line1.getRegex(), equalTo(as2));
assertThat(line2.getRegex(), equalTo(as3));
assertThat(line3.getRegex(), equalTo(as4));
assertThat(line4.getRegex(), equalTo(as1));
assertThat(line5.getRegex(), equalTo(as2));
assertThat(line6.getRegex(), equalTo(as3));
assertThat(line7.getRegex(), equalTo(as4));

// Check that the AS-path access list definition was registered
DefinedStructureInfo definedStructureInfo =
getDefinedStructureInfo(CumulusStructureType.IP_AS_PATH_ACCESS_LIST, name);
assertThat(definedStructureInfo.getDefinitionLines().enumerate(), contains(1, 2));
assertThat(
definedStructureInfo.getDefinitionLines().enumerate(), contains(1, 2, 3, 4, 5, 6, 7, 8));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,37 +258,41 @@ public void testToRouteFilter() {

@Test
public void testToAsPathAccessList() {
long permitted = 11111;
long denied = 22222;
String permitted_regex = "(^| )11111($| )";
String denied_regex = "(^| )22222($| )";

IpAsPathAccessList asPathAccessList = new IpAsPathAccessList("name");
asPathAccessList.addLine(new IpAsPathAccessListLine(LineAction.DENY, denied));
asPathAccessList.addLine(new IpAsPathAccessListLine(LineAction.PERMIT, permitted));
asPathAccessList.addLine(new IpAsPathAccessListLine(LineAction.DENY, denied_regex));
asPathAccessList.addLine(new IpAsPathAccessListLine(LineAction.PERMIT, permitted_regex));
AsPathAccessList viList = toAsPathAccessList(asPathAccessList);

// Cache initialization only happens in AsPathAccessList on deserialization o.O
viList = SerializationUtils.clone(viList);

List<AsPathAccessListLine> expectedViLines =
ImmutableList.of(
new AsPathAccessListLine(LineAction.DENY, String.format("(^| )%s($| )", denied)),
new AsPathAccessListLine(LineAction.PERMIT, String.format("(^| )%s($| )", permitted)));
new AsPathAccessListLine(LineAction.DENY, denied_regex),
new AsPathAccessListLine(LineAction.PERMIT, permitted_regex));
assertThat(viList, equalTo(new AsPathAccessList("name", expectedViLines)));

// Matches paths containing permitted ASN
long other = 33333;
assertTrue(viList.permits(AsPath.ofSingletonAsSets(permitted)));
assertTrue(viList.permits(AsPath.ofSingletonAsSets(permitted, other)));
assertTrue(viList.permits(AsPath.ofSingletonAsSets(other, permitted)));
assertTrue(viList.permits(AsPath.ofSingletonAsSets(other, permitted, other)));
long permitted_asn = 11111;
long denied_asn = 22222;
long other_asn = 33333;

assertTrue(viList.permits(AsPath.ofSingletonAsSets(permitted_asn)));
assertTrue(viList.permits(AsPath.ofSingletonAsSets(permitted_asn, other_asn)));
assertTrue(viList.permits(AsPath.ofSingletonAsSets(other_asn, permitted_asn)));
assertTrue(viList.permits(AsPath.ofSingletonAsSets(other_asn, permitted_asn, other_asn)));

// Does not match if denied ASN is in path, even if permitted is also there
assertFalse(viList.permits(AsPath.ofSingletonAsSets(denied)));
assertFalse(viList.permits(AsPath.ofSingletonAsSets(denied, permitted)));
assertFalse(viList.permits(AsPath.ofSingletonAsSets(permitted, denied)));
assertFalse(viList.permits(AsPath.ofSingletonAsSets(permitted, denied, permitted)));
assertFalse(viList.permits(AsPath.ofSingletonAsSets(denied_asn)));
assertFalse(viList.permits(AsPath.ofSingletonAsSets(denied_asn, permitted_asn)));
assertFalse(viList.permits(AsPath.ofSingletonAsSets(permitted_asn, denied_asn)));
assertFalse(viList.permits(AsPath.ofSingletonAsSets(permitted_asn, denied_asn, permitted_asn)));

// Does not match by default
assertFalse(viList.permits(AsPath.ofSingletonAsSets(other)));
assertFalse(viList.permits(AsPath.ofSingletonAsSets(other_asn)));
}

@Test
Expand Down

0 comments on commit 064c6a6

Please sign in to comment.