package org.batfish.grammar.flatjuniper; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.antlr.v4.runtime.CommonToken; import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.TerminalNode; import org.antlr.v4.runtime.tree.TerminalNodeImpl; import org.batfish.grammar.flatjuniper.FlatJuniperParser.Deactivate_lineContext; import org.batfish.grammar.flatjuniper.FlatJuniperParser.Flat_juniper_configurationContext; import org.batfish.grammar.flatjuniper.FlatJuniperParser.Set_lineContext; import org.batfish.grammar.flatjuniper.FlatJuniperParser.Set_line_tailContext; import org.batfish.grammar.flatjuniper.FlatJuniperParser.StatementContext; import org.batfish.grammar.flatjuniper.Hierarchy.HierarchyTree.HierarchyPath; import org.batfish.common.BatfishException; import org.batfish.datamodel.Ip; import org.batfish.datamodel.Ip6; import org.batfish.datamodel.Prefix; import org.batfish.datamodel.Prefix6; import org.batfish.main.Settings; import org.batfish.main.PartialGroupMatchException; import org.batfish.main.UndefinedGroupBatfishException; public class Hierarchy { public static class HierarchyTree { private enum AddPathResult { BLACKLISTED, MODIFIED, UNMODIFIED } private static abstract class HierarchyChildNode extends HierarchyNode { private Set_lineContext _line; protected String _sourceGroup; public List<String> _sourceWildcards; protected String _text; private HierarchyChildNode(String text) { _text = text; } public abstract HierarchyChildNode copy(); public abstract boolean isMatchedBy(HierarchyLiteralNode node); public abstract boolean isMatchedBy(HierarchyWildcardNode node); public abstract boolean matches(HierarchyChildNode node); } private static final class HierarchyLiteralNode extends HierarchyChildNode { private HierarchyLiteralNode(String text) { super(text); } @Override public HierarchyChildNode copy() { return new HierarchyLiteralNode(_text); } @Override public boolean isMatchedBy(HierarchyLiteralNode node) { return _text.equals(node._text); } @Override public boolean isMatchedBy(HierarchyWildcardNode node) { String regex = node._wildcard.replaceAll("\\*", ".*"); return _text.matches(regex); } @Override public boolean matches(HierarchyChildNode node) { return node.isMatchedBy(this); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Literal(" + _text); if (_sourceGroup != null) { sb.append(", srcGroup:\"" + _sourceGroup + "\""); } if (_sourceWildcards != null) { sb.append(", srcWildcard:\"" + _sourceWildcards + "\""); } sb.append(")"); return sb.toString(); } } private static abstract class HierarchyNode { protected Set<String> _blacklistedGroups; private Map<String, HierarchyChildNode> _children; public HierarchyNode() { _children = new LinkedHashMap<>(); _blacklistedGroups = new HashSet<>(); } public void addBlacklistedGroup(String groupName) { _blacklistedGroups.add(groupName); } public void addChildNode(HierarchyChildNode node) { _children.put(node._text, node); } public HierarchyChildNode getChildNode(String text) { return _children.get(text); } public Map<String, HierarchyChildNode> getChildren() { return _children; } public HierarchyChildNode getFirstMatchingChildNode( HierarchyChildNode node) { for (HierarchyChildNode child : _children.values()) { if (child.matches(node)) { return child; } } return null; } public boolean isWildcard() { return false; } } public static final class HierarchyPath { private boolean _containsWildcard; private List<HierarchyChildNode> _nodes; private StatementContext _statement; public HierarchyPath() { _nodes = new ArrayList<>(); } public void addNode(String text) { HierarchyChildNode newNode = new HierarchyLiteralNode(text); _nodes.add(newNode); } public void addWildcardNode(String text) { _containsWildcard = true; HierarchyChildNode newNode = new HierarchyWildcardNode(text); _nodes.add(newNode); } public boolean containsWildcard() { return _containsWildcard; } public String pathString() { StringBuilder sb = new StringBuilder(); for (HierarchyChildNode node : _nodes) { sb.append(node._text + " "); } String out = sb.toString().trim(); return out; } public void setStatement(StatementContext statement) { _statement = statement; } @Override public String toString() { return "Path(Statement:" + _statement + "," + _nodes + ")"; } } private static final class HierarchyRootNode extends HierarchyNode { } private static final class HierarchyWildcardNode extends HierarchyChildNode { private String _wildcard; private HierarchyWildcardNode(String text) { super(text); if (text.charAt(0) != '<' || text.charAt(text.length() - 1) != '>') { throw new BatfishException( "Improperly-formatted wildcard: " + text); } _wildcard = text.substring(1, text.length() - 1); } @Override public HierarchyChildNode copy() { return new HierarchyWildcardNode(_text); } @Override public boolean isMatchedBy(HierarchyLiteralNode node) { return false; } @Override public boolean isMatchedBy(HierarchyWildcardNode node) { // TODO: check whether this is the only way to match two wildcards return _text.equals(node._text); } @Override public boolean isWildcard() { return true; } @Override public boolean matches(HierarchyChildNode node) { return node.isMatchedBy(this); } @Override public String toString() { return "Wildcard(" + _text + ")"; } } private static Settings parserSettings() { Settings settings = new Settings(); settings.setThrowOnLexerError(true); settings.setThrowOnParserError(true); return settings; } private String _groupName; private HierarchyRootNode _root; private HierarchyTree(String groupName) { _groupName = groupName; _root = new HierarchyRootNode(); } private void addGroupPaths(Set_lineContext groupLine, Collection<HierarchyChildNode> currentGroupChildren, HierarchyTree masterTree, HierarchyPath path, List<ParseTree> lines, Flat_juniper_configurationContext configurationContext) { if (groupLine != null) { Set_lineContext setLine = new Set_lineContext(configurationContext, -1); if (masterTree.addPath(path, setLine, _groupName) == AddPathResult.BLACKLISTED) { return; } StringBuilder sb = new StringBuilder(); for (HierarchyChildNode pathNode : path._nodes) { sb.append(pathNode._text + " "); } String newStatementText = sb.toString(); // get rid of last " ", which matters for tokens where whitespace is // not ignored newStatementText = "set " + newStatementText.substring(0, newStatementText.length() - 1) + "\n"; TerminalNode set = new TerminalNodeImpl( new CommonToken(FlatJuniperLexer.SET, "set")); Set_line_tailContext setLineTail = new Set_line_tailContext(setLine, -1); TerminalNode newline = new TerminalNodeImpl( new CommonToken(FlatJuniperLexer.NEWLINE, "\n")); setLine.children = new ArrayList<ParseTree>(); setLine.children.add(set); setLine.children.add(setLineTail); setLine.children.add(newline); Settings settings = parserSettings(); FlatJuniperCombinedParser parser = new FlatJuniperCombinedParser( newStatementText, settings); Flat_juniper_configurationContext newConfiguration = parser .getParser().flat_juniper_configuration(); // StatementContext newStatement = parser.getParser().statement(); StatementContext newStatement = newConfiguration.set_line(0) .set_line_tail().statement(); newStatement.parent = setLineTail; setLineTail.children = new ArrayList<ParseTree>(); setLineTail.children.add(newStatement); lines.add(setLine); } for (HierarchyChildNode childNode : currentGroupChildren) { HierarchyChildNode newPathNode = childNode.copy(); path._nodes.add(newPathNode); addGroupPaths(childNode._line, childNode.getChildren().values(), masterTree, path, lines, configurationContext); path._nodes.remove(path._nodes.size() - 1); } } public AddPathResult addPath(HierarchyPath path, Set_lineContext ctx, String group) { AddPathResult result = AddPathResult.UNMODIFIED; HierarchyNode currentNode = _root; HierarchyChildNode matchNode = null; for (HierarchyChildNode currentPathNode : path._nodes) { matchNode = currentNode.getChildNode(currentPathNode._text); if (matchNode == null) { result = AddPathResult.MODIFIED; matchNode = currentPathNode.copy(); currentNode.addChildNode(matchNode); } if (matchNode._blacklistedGroups.contains(group)) { return AddPathResult.BLACKLISTED; } currentNode = matchNode; } matchNode._line = ctx; matchNode._sourceGroup = group; return result; } public List<ParseTree> applyWildcardPath(HierarchyPath path, Flat_juniper_configurationContext configurationContext) { HierarchyChildNode wildcardNode = findExactPathMatchNode(path); String sourceGroup = wildcardNode._sourceGroup; int remainingWildcards = 0; for (HierarchyChildNode node : path._nodes) { if (node.isWildcard()) { remainingWildcards++; } } List<String> appliedWildcards = new ArrayList<>(); HierarchyPath newPath = new HierarchyPath(); List<ParseTree> lines = new ArrayList<>(); applyWildcardPath(path, configurationContext, sourceGroup, _root, 0, remainingWildcards, appliedWildcards, newPath, lines); return lines; } private void applyWildcardPath(HierarchyPath path, Flat_juniper_configurationContext configurationContext, String sourceGroup, HierarchyNode destinationTreeRoot, int startingIndex, int remainingWildcards, List<String> appliedWildcards, HierarchyPath newPath, List<ParseTree> lines) { if (destinationTreeRoot._blacklistedGroups.contains(sourceGroup)) { return; } HierarchyChildNode currentPathNode = path._nodes.get(startingIndex); if (!currentPathNode.isWildcard()) { String currentPathNodeText = currentPathNode._text; HierarchyChildNode newDestinationTreeRoot = destinationTreeRoot .getChildNode(currentPathNodeText); if (newDestinationTreeRoot == null) { // If literal node does not exist, but there are still more // wildcards to match, we abort. // Else, we create node and continue recursing if (remainingWildcards > 0) { return; } newDestinationTreeRoot = currentPathNode.copy(); destinationTreeRoot._children.put(newDestinationTreeRoot._text, newDestinationTreeRoot); } newPath._nodes.add(newDestinationTreeRoot); if (startingIndex == path._nodes.size() - 1) { newDestinationTreeRoot._sourceWildcards = new ArrayList<>(); newDestinationTreeRoot._sourceWildcards.addAll(appliedWildcards); newDestinationTreeRoot._line = generateSetLine(newPath, configurationContext); lines.add(newDestinationTreeRoot._line); } else { applyWildcardPath(path, configurationContext, sourceGroup, newDestinationTreeRoot, startingIndex + 1, remainingWildcards, appliedWildcards, newPath, lines); } newPath._nodes.remove(newPath._nodes.size() - 1); } else { appliedWildcards.add(currentPathNode._text); if (startingIndex < path._nodes.size() - 1) { for (HierarchyChildNode destinationTreeNode : destinationTreeRoot._children .values()) { // if there are no matching children, then we recurse no // further if (!destinationTreeNode.isWildcard() && currentPathNode.matches(destinationTreeNode)) { newPath._nodes.add(destinationTreeNode); applyWildcardPath(path, configurationContext, sourceGroup, destinationTreeNode, startingIndex + 1, remainingWildcards - 1, appliedWildcards, newPath, lines); newPath._nodes.remove(newPath._nodes.size() - 1); } } } appliedWildcards.remove(appliedWildcards.size() - 1); } } public boolean containsPathPrefixOf(HierarchyPath path) { Map<String, HierarchyChildNode> currentChildren = _root.getChildren(); List<HierarchyChildNode> pathNodes = path._nodes; for (HierarchyChildNode currentPathNode : pathNodes) { HierarchyChildNode treeMatchNode = currentChildren .get(currentPathNode._text); if (treeMatchNode != null) { if (treeMatchNode.getChildren().size() == 0) { return true; } else { currentChildren = treeMatchNode.getChildren(); } } else { break; } } return false; } private HierarchyChildNode findExactPathMatchNode(HierarchyPath path) { HierarchyNode currentGroupNode = _root; HierarchyChildNode matchNode = null; for (HierarchyChildNode currentPathNode : path._nodes) { matchNode = currentGroupNode.getChildNode(currentPathNode._text); currentGroupNode = matchNode; } return matchNode; } private Set_lineContext generateSetLine(HierarchyPath path, Flat_juniper_configurationContext configurationContext) { Set_lineContext setLine = new Set_lineContext(configurationContext, -1); StringBuilder sb = new StringBuilder(); for (HierarchyChildNode pathNode : path._nodes) { sb.append(pathNode._text + " "); } String newStatementText = sb.toString(); newStatementText = "set " + newStatementText.substring(0, newStatementText.length() - 1) + "\n"; TerminalNode set = new TerminalNodeImpl( new CommonToken(FlatJuniperLexer.SET, "set")); Set_line_tailContext setLineTail = new Set_line_tailContext(setLine, -1); TerminalNode newline = new TerminalNodeImpl( new CommonToken(FlatJuniperLexer.NEWLINE, "\n")); setLine.children = new ArrayList<ParseTree>(); setLine.children.add(set); setLine.children.add(setLineTail); setLine.children.add(newline); Settings settings = parserSettings(); FlatJuniperCombinedParser parser = new FlatJuniperCombinedParser( newStatementText, settings); Flat_juniper_configurationContext newConfiguration = parser.getParser() .flat_juniper_configuration(); StatementContext newStatement = newConfiguration.set_line(0) .set_line_tail().statement(); newStatement.parent = setLineTail; setLineTail.children = new ArrayList<ParseTree>(); setLineTail.children.add(newStatement); return setLine; } public List<ParseTree> getApplyGroupsLines(HierarchyPath path, Flat_juniper_configurationContext configurationContext, HierarchyTree masterTree) { List<ParseTree> lines = new ArrayList<>(); HierarchyNode currentGroupNode = _root; HierarchyChildNode matchNode = null; HierarchyPath partialMatch = new HierarchyPath(); if (path._nodes.size() == 0) { addGroupPaths(null, _root.getChildren().values(), masterTree, path, lines, configurationContext); } else { for (HierarchyChildNode currentPathNode : path._nodes) { matchNode = currentGroupNode .getFirstMatchingChildNode(currentPathNode); if (matchNode == null) { String message = "No matching path"; if (partialMatch._nodes.size() > 0) { message += ": Partial path match within applied group: \"" + partialMatch.pathString() + "\""; } throw new PartialGroupMatchException(message); } partialMatch._nodes.add(matchNode); currentGroupNode = matchNode; } // at this point, matchNode is the node in the group tree whose // children must be added to the main tree with substitutions // applied // according to the supplied path addGroupPaths(matchNode._line, matchNode.getChildren().values(), masterTree, path, lines, configurationContext); } return lines; } public List<ParseTree> getApplyPathLines(HierarchyPath basePath, HierarchyPath applyPathPath, Flat_juniper_configurationContext configurationContext) { List<ParseTree> lines = new ArrayList<>(); List<String> candidatePrefixes = getApplyPathPrefixes(applyPathPath); for (String candidatePrefix : candidatePrefixes) { String finalPrefixStr; boolean ipv6 = candidatePrefix.contains(":"); boolean isPrefix = candidatePrefix.contains("/"); try { if (isPrefix) { if (ipv6) { Prefix6 prefix6 = new Prefix6(candidatePrefix); finalPrefixStr = prefix6.toString(); } else { Prefix prefix = new Prefix(candidatePrefix); finalPrefixStr = prefix.toString(); } } else { String candidateAddress; if (ipv6) { candidateAddress = new Ip6(candidatePrefix).toString(); } else { candidateAddress = new Ip(candidatePrefix).toString(); } finalPrefixStr = candidateAddress + (ipv6 ? "/64" : "/32"); } } catch (BatfishException e) { throw new BatfishException("Invalid ip(v6) address or prefix: \"" + candidatePrefix + "\"", e); } basePath.addNode(finalPrefixStr); Set_lineContext setLine = generateSetLine(basePath, configurationContext); lines.add(setLine); basePath._nodes.remove(basePath._nodes.size() - 1); } return lines; } private List<String> getApplyPathPrefixes(HierarchyPath path) { List<String> prefixes = new ArrayList<>(); getApplyPathPrefixes(path, _root, 0, prefixes); return prefixes; } private void getApplyPathPrefixes(HierarchyPath path, HierarchyNode currentNode, int currentDepth, List<String> prefixes) { if (currentDepth == path._nodes.size() - 1) { for (HierarchyChildNode currentChild : currentNode.getChildren() .values()) { if (!currentChild.isWildcard()) { prefixes.add(currentChild._text); } } } else { HierarchyChildNode currentPathNode = path._nodes.get(currentDepth); for (HierarchyChildNode currentChild : currentNode.getChildren() .values()) { if (currentPathNode.matches(currentChild)) { getApplyPathPrefixes(path, currentChild, currentDepth + 1, prefixes); } } } } public String getGroupName() { return _groupName; } public void pruneAfterPath(HierarchyPath path) { HierarchyChildNode pathEnd = findExactPathMatchNode(path); pathEnd.getChildren().clear(); } public void setApplyGroupsExcept(HierarchyPath path, String groupName) { HierarchyChildNode node = findExactPathMatchNode(path); node.addBlacklistedGroup(groupName); } } private HierarchyTree _deactivateTree; private HierarchyTree _masterTree; private Map<String, HierarchyTree> _trees; public Hierarchy() { _trees = new HashMap<>(); _masterTree = new HierarchyTree(null); _deactivateTree = new HierarchyTree(null); } public void addDeactivatePath(HierarchyPath path, Deactivate_lineContext ctx) { if (isDeactivated(path)) { return; } _deactivateTree.addPath(path, null, null); _deactivateTree.pruneAfterPath(path); } public void addMasterPath(HierarchyPath path, Set_lineContext ctx) { _masterTree.addPath(path, ctx, null); } public List<ParseTree> getApplyGroupsLines(String groupName, HierarchyPath path, Flat_juniper_configurationContext configurationContext) { HierarchyTree tree = _trees.get(groupName); if (tree == null) { throw new UndefinedGroupBatfishException( "No such group: \"" + groupName + "\""); } return tree.getApplyGroupsLines(path, configurationContext, _masterTree); } public List<ParseTree> getApplyPathLines(HierarchyPath basePath, HierarchyPath applyPathPath, Flat_juniper_configurationContext configurationContext) { return _masterTree.getApplyPathLines(basePath, applyPathPath, configurationContext); } public HierarchyTree getMasterTree() { return _masterTree; } public HierarchyTree getTree(String groupName) { return _trees.get(groupName); } public boolean isDeactivated(HierarchyPath path) { return _deactivateTree.containsPathPrefixOf(path); } public HierarchyTree newTree(String groupName) { HierarchyTree newTree = new HierarchyTree(groupName); _trees.put(groupName, newTree); return newTree; } public void setApplyGroupsExcept(HierarchyPath path, String groupName) { _masterTree.setApplyGroupsExcept(path, groupName); } }