Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recognize certain Junos AS Path regexes and convert them to VI construct #8770

Merged
merged 6 commits into from
Aug 14, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package org.batfish.representation.juniper;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Range;
import org.batfish.datamodel.routing_policy.as_path.AsPathMatchExpr;
import org.batfish.datamodel.routing_policy.as_path.AsPathMatchRegex;
import org.batfish.datamodel.routing_policy.as_path.AsSetsMatchingRanges;
import org.batfish.representation.juniper.parboiled.AsPathRegex;


import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.google.common.base.Preconditions.checkArgument;

/**
* A class that converts a Juniper AS Path regex to an instance of {@link AsPathMatchExpr}.
*
* @see <a
* href="https://www.juniper.net/documentation/en_US/junos/topics/usage-guidelines/policy-configuring-as-path-regular-expressions-to-use-as-routing-policy-match-conditions.html">Juniper
* docs</a>
*/
public final class AsPathMatchExprParser {

// "N" : "AS Path matches single ASN number"
private static final Pattern AS_PATH_EXACT_MATCH_ASN = Pattern.compile("(\\d+)");

// ".* N .*" : "AS Path contains N"
private static final Pattern AS_PATH_CONTAINS_ASN = Pattern.compile("\\.\\*\\s?(\\d+)\\s?\\.\\*");

// ".* N" or ".* N$" : "AS Path ends with N"
private static final Pattern AS_PATH_ENDS_WITH_ASN = Pattern.compile("\\.\\* (\\d+)\\$?");

// "N .*" or "^N .*" : "AS Path starts with N".
private static final Pattern AS_PATH_STARTS_WITH_ASN = Pattern.compile("\\^?(\\d+) \\.\\*");

// ".* [start-end] .*" : "AS Path contains an ASN in range between start and end included
private static final Pattern AS_PATH_CONTAINS_ASN_RANGE_PATTERN_1 = Pattern.compile("\\.\\* \\[(\\d+)-(\\d+)\\] \\.\\*");

// ".* start-end .*" : "AS Path contains an ASN in range between start and end included
private static final Pattern AS_PATH_CONTAINS_ASN_RANGE_PATTERN_2 = Pattern.compile("\\.\\* (\\d+)-(\\d+) \\.\\*");

// "[start-end]" : "AS Path matches single ASN number in range between start and end included
private static final Pattern AS_PATH_EXACT_MATCH_ASN_RANGE_PATTERN_1 = Pattern.compile("\\[(\\d+)-(\\d+)\\]");

// "start-end" : "AS Path matches single ASN number in range between start and end included
private static final Pattern AS_PATH_EXACT_MATCH_ASN_RANGE_PATTERN_2 = Pattern.compile("(\\d+)-(\\d+)");

/**
* Converts the given Juniper AS Path regular expression to an instance of {@link AsPathMatchExpr}.
* First match the AS path input regex against predefined AS path regexes and return VI construct {@link AsSetsMatchingRanges}
* Else convert to java regex and return {@link AsPathMatchRegex}
*/
public static AsPathMatchExpr convertToAsPathMatchExpr(String asPathRegex) {

Matcher asPathExactMatchAsn = AS_PATH_EXACT_MATCH_ASN.matcher(asPathRegex);
if (asPathExactMatchAsn.matches()) {
return getAsSetsMatchingRanges(asPathExactMatchAsn.group(1), true, true);
}

Matcher asPathContainsAsn = AS_PATH_CONTAINS_ASN.matcher(asPathRegex);
if (asPathContainsAsn.matches()) {
return getAsSetsMatchingRanges(asPathContainsAsn.group(1), false, false);
}

Matcher asPathStartsWithAsn = AS_PATH_STARTS_WITH_ASN.matcher(asPathRegex);
if (asPathStartsWithAsn.matches()) {
return getAsSetsMatchingRanges(asPathStartsWithAsn.group(1), false, true);
}

Matcher asPathEndsWithAsn = AS_PATH_ENDS_WITH_ASN.matcher(asPathRegex);
if (asPathEndsWithAsn.matches()) {
return getAsSetsMatchingRanges(asPathEndsWithAsn.group(1), true, false);
}

Matcher asPathContainsAsnRangeBrackets = AS_PATH_CONTAINS_ASN_RANGE_PATTERN_1.matcher(asPathRegex);
if (asPathContainsAsnRangeBrackets.matches()) {
return getAsSetsMatchingRanges(asPathContainsAsnRangeBrackets.group(1), asPathContainsAsnRangeBrackets.group(2), false, false);
}

Matcher asPathContainsAsnRangeNoBrackets = AS_PATH_CONTAINS_ASN_RANGE_PATTERN_2.matcher(asPathRegex);
if (asPathContainsAsnRangeNoBrackets.matches()) {
return getAsSetsMatchingRanges(asPathContainsAsnRangeNoBrackets.group(1), asPathContainsAsnRangeNoBrackets.group(2), false, false);
}

Matcher asPathExactMatchAsnRangeBrackets = AS_PATH_EXACT_MATCH_ASN_RANGE_PATTERN_1.matcher(asPathRegex);
if (asPathExactMatchAsnRangeBrackets.matches()) {
return getAsSetsMatchingRanges(asPathExactMatchAsnRangeBrackets.group(1), asPathExactMatchAsnRangeBrackets.group(2), true, true);
}

Matcher asPathExactMatchAsnRangeNoBrackets = AS_PATH_EXACT_MATCH_ASN_RANGE_PATTERN_2.matcher(asPathRegex);
if (asPathExactMatchAsnRangeNoBrackets.matches()) {
return getAsSetsMatchingRanges(asPathExactMatchAsnRangeNoBrackets.group(1), asPathExactMatchAsnRangeNoBrackets.group(2), true, true);
}

String javaRegex = AsPathRegex.convertToJavaRegex(asPathRegex);
return AsPathMatchRegex.of(javaRegex);
}

private static AsSetsMatchingRanges getAsSetsMatchingRanges(String asn, boolean anchorEnd, boolean anchorStart) {
long asnLong = Long.parseLong(asn);
return AsSetsMatchingRanges.of(anchorEnd, anchorStart, ImmutableList.of(Range.singleton(asnLong)));
}

private static AsSetsMatchingRanges getAsSetsMatchingRanges(String asnLowerRange, String asnUpperRange, boolean anchorEnd, boolean anchorStart) {
long start = Long.parseLong(asnLowerRange);
long end = Long.parseLong(asnUpperRange);
checkArgument(start <= end, "Invalid range %s-%s", start, end);
return AsSetsMatchingRanges.of(anchorEnd, anchorStart, ImmutableList.of(Range.closed(start, end)));
}


private AsPathMatchExprParser() {} // prevent instantiation of utility class.
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import javax.annotation.Nullable;
import org.batfish.common.Warnings;
import org.batfish.datamodel.Configuration;
import org.batfish.datamodel.routing_policy.as_path.AsPathMatchRegex;
import org.batfish.datamodel.routing_policy.as_path.AsPathMatchExpr;
import org.batfish.datamodel.routing_policy.as_path.InputAsPath;
import org.batfish.datamodel.routing_policy.as_path.MatchAsPath;
import org.batfish.datamodel.routing_policy.expr.BooleanExpr;
Expand Down Expand Up @@ -33,8 +33,8 @@ static BooleanExpr toBooleanExpr(@Nullable AsPath asPath, Warnings w) {
return BooleanExprs.FALSE;
}
try {
String javaRegex = AsPathRegex.convertToJavaRegex(asPath.getRegex());
return MatchAsPath.of(InputAsPath.instance(), AsPathMatchRegex.of(javaRegex));
AsPathMatchExpr asPathMatchExpr = AsPathMatchExprParser.convertToAsPathMatchExpr(asPath.getRegex());
return MatchAsPath.of(InputAsPath.instance(), asPathMatchExpr);
} catch (Exception e) {
w.redFlag(
String.format(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
package org.batfish.representation.juniper;

import static org.batfish.representation.juniper.parboiled.AsPathRegex.convertToJavaRegex;

import com.google.common.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import org.batfish.common.Warnings;
import org.batfish.datamodel.Configuration;
import org.batfish.datamodel.routing_policy.as_path.AsPathMatchRegex;
import org.batfish.datamodel.routing_policy.as_path.AsPathMatchExpr;
import org.batfish.datamodel.routing_policy.as_path.InputAsPath;
import org.batfish.datamodel.routing_policy.as_path.MatchAsPath;
import org.batfish.datamodel.routing_policy.expr.BooleanExpr;
Expand Down Expand Up @@ -38,8 +36,8 @@ static BooleanExpr toBooleanExpr(@Nullable AsPathGroup asPathGroup, Warnings w)
List<BooleanExpr> asPaths = new ArrayList<>();
for (NamedAsPath namedAsPath : asPathGroup.getAsPaths().values()) {
try {
String convertedVIRegex = convertToJavaRegex(namedAsPath.getRegex());
asPaths.add(MatchAsPath.of(InputAsPath.instance(), AsPathMatchRegex.of(convertedVIRegex)));
AsPathMatchExpr asPathMatchExpr = AsPathMatchExprParser.convertToAsPathMatchExpr(namedAsPath.getRegex());
asPaths.add(MatchAsPath.of(InputAsPath.instance(), asPathMatchExpr));
} catch (Exception e) {
w.redFlag(
String.format(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package org.batfish.representation.juniper;

import org.batfish.datamodel.AsPath;
import org.batfish.datamodel.routing_policy.as_path.AsPathContext;
import org.batfish.datamodel.routing_policy.as_path.AsPathMatchExpr;
import org.batfish.datamodel.routing_policy.as_path.AsPathMatchExprEvaluator;
import org.batfish.datamodel.routing_policy.as_path.AsPathMatchRegex;
import org.batfish.datamodel.routing_policy.as_path.AsSetsMatchingRanges;
import org.junit.Test;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static org.batfish.representation.juniper.AsPathMatchExprParser.convertToAsPathMatchExpr;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertFalse;

/**
* Tests of {@link AsPathMatchExprParser}.
*
* @see <a
* href="https://www.juniper.net/documentation/en_US/junos/topics/usage-guidelines/policy-configuring-as-path-regular-expressions-to-use-as-routing-policy-match-conditions.html">Juniper
* docs</a>
*/
public class AsPathMatchExprParserTest {

private final AsPathMatchExprEvaluator evaluator = new AsPathMatchExprEvaluator(AsPathContext.builder().setInputAsPath(AsPath.empty()).build());

private void assertMatches(AsPathMatchExpr res, Long... asPath) {
assertTrue(res.accept(evaluator, AsPath.ofSingletonAsSets(asPath)));
}

private void assertDoesNotMatch(AsPathMatchExpr res, Long... asPath) {
assertFalse(res.accept(evaluator, AsPath.ofSingletonAsSets(asPath)));
}

@Test
public void testMatchingSingleNumber() {
AsPathMatchExpr res = convertToAsPathMatchExpr("1234");
assertThat(res, instanceOf(AsSetsMatchingRanges.class)); // did not fall back to regex
assertMatches(res, 1234L);
assertDoesNotMatch(res);
assertDoesNotMatch(res, 123L);
assertDoesNotMatch(res, 1234L, 1L);
assertDoesNotMatch(res, 1L, 1234L, 1L);
assertDoesNotMatch(res, 1L, 2L, 3L, 4L);
}

@Test
public void testContainsAsn() {
AsPathMatchExpr res = convertToAsPathMatchExpr(".* 1234 .*");
assertThat(res, instanceOf(AsSetsMatchingRanges.class)); // did not fall back to regex
assertMatches(res, 1234L);
assertMatches(res, 56L, 1234L);
assertMatches(res, 1L, 2L, 1234L);
assertMatches(res, 1234L, 1L, 2L);
assertMatches(res, 5L, 1234L, 8L);
assertDoesNotMatch(res);
assertDoesNotMatch(res, 1L, 2L, 3L, 124L);
}

@Test
public void testStartsWithAsn() {
for (String regex : new String[] {"1234 .*", "^1234 .*"}) {
AsPathMatchExpr res = convertToAsPathMatchExpr(regex);
assertThat(res, instanceOf(AsSetsMatchingRanges.class)); // did not fall back to regex
assertMatches(res, 1234L);
assertMatches(res, 1234L, 12L);
assertMatches(res, 1234L, 56L, 78L);
assertDoesNotMatch(res);
assertDoesNotMatch(res, 12L, 1234L);
assertDoesNotMatch(res, 1L, 2L, 1234L);
assertDoesNotMatch(res, 5L, 1234L, 8L);
}
}

@Test
public void testEndsWithAsn() {
for (String regex : new String[] {".* 1234", ".* 1234$"}) {
AsPathMatchExpr res = convertToAsPathMatchExpr(regex);
assertThat(res, instanceOf(AsSetsMatchingRanges.class)); // did not fall back to regex
assertMatches(res, 1234L);
assertMatches(res, 12L, 1234L);
assertMatches(res, 56L, 78L, 1234L);
assertDoesNotMatch(res);
assertDoesNotMatch(res, 1234L, 12L);
assertDoesNotMatch(res, 1234L, 1L, 8L);
assertDoesNotMatch(res, 5L, 1234L, 8L);
}
}

@Test
public void testContainsAsnRange() {
for (String regex : new String[] {".* [123-187] .*", ".* 123-187 .*"}) {
AsPathMatchExpr res = convertToAsPathMatchExpr(regex);
assertThat(res, instanceOf(AsSetsMatchingRanges.class)); // did not fall back to regex
assertMatches(res, 123L);
assertMatches(res, 135L);
assertMatches(res, 187L);
assertMatches(res, 110L, 123L, 135L, 2L);
assertDoesNotMatch(res);
assertDoesNotMatch(res, 0L);
assertDoesNotMatch(res, 1102L);
assertDoesNotMatch(res, 110L, 122L, 188L);
}
}

@Test
public void testSingleAsnRange() {
for (String regex : new String[] {"[123-187]", "123-187"}) {
AsPathMatchExpr res = convertToAsPathMatchExpr(regex);
assertThat(res, instanceOf(AsSetsMatchingRanges.class)); // did not fall back to regex
assertMatches(res, 123L);
assertMatches(res, 135L);
assertMatches(res, 187L);
assertDoesNotMatch(res, 110L, 123L, 135L, 2L);
assertDoesNotMatch(res);
assertDoesNotMatch(res, 0L);
assertDoesNotMatch(res, 1102L);
assertDoesNotMatch(res, 110L, 122L, 188L);
}
}

/**
* Test for fallback to {@link AsPathMatchRegex} for some example regexes tested in {@link AsPathRegexTest}.
*/
@Test
public void testAsPathMatchRegexFallback() {
for (String regex : new String[]{"1234?", "1234{0,1}", "12{1,4} 34", "123 (56 | 78)?", "()"}) {
AsPathMatchExpr res1 = convertToAsPathMatchExpr(regex);
assertThat(res1, instanceOf(AsPathMatchRegex.class));
}
// Sanity testing that the fallback delegation AsPathMatchRegex works as expected
AsPathMatchExpr res2 = convertToAsPathMatchExpr("1 | (2 3) | (4 (5|6))");
assertThat(res2, instanceOf(AsPathMatchRegex.class));; // fall back to regex
assertMatches(res2,1L);
assertMatches(res2,2L, 3L);
assertMatches(res2,4L, 5L);
assertMatches(res2,4L, 6L);
assertDoesNotMatch(res2,1L, 2L, 3L);
}

}