package org.batfish.job; import java.nio.file.Path; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.antlr.v4.runtime.ParserRuleContext; import org.batfish.grammar.BatfishCombinedParser; import org.batfish.grammar.ControlPlaneExtractor; import org.batfish.grammar.VendorConfigurationFormatDetector; import org.batfish.grammar.ParseTreePrettyPrinter; import org.batfish.grammar.cisco.CiscoCombinedParser; import org.batfish.grammar.cisco.CiscoControlPlaneExtractor; import org.batfish.grammar.flatjuniper.FlatJuniperCombinedParser; import org.batfish.grammar.flatjuniper.FlatJuniperControlPlaneExtractor; import org.batfish.grammar.flatvyos.FlatVyosCombinedParser; import org.batfish.grammar.flatvyos.FlatVyosControlPlaneExtractor; import org.batfish.grammar.iptables.IptablesCombinedParser; import org.batfish.grammar.iptables.IptablesControlPlaneExtractor; import org.batfish.grammar.mrv.MrvCombinedParser; import org.batfish.grammar.mrv.MrvControlPlaneExtractor; import org.batfish.main.Batfish; import org.batfish.main.Settings; import org.batfish.common.BatfishException; import org.batfish.common.ParseTreeSentences; import org.batfish.datamodel.ConfigurationFormat; import org.batfish.datamodel.answers.ParseStatus; import org.batfish.main.ParserBatfishException; import org.batfish.common.Warnings; import org.batfish.representation.host.HostConfiguration; import org.batfish.vendor.VendorConfiguration; public class ParseVendorConfigurationJob extends BatfishJob<ParseVendorConfigurationResult> { private static Pattern BANNER_PATTERN = Pattern.compile( "(?m)banner[ \t][ \t]*[^ \r\n\t][^ \r\n\t]*[ \t][ \t]*([^ \r\n\t])[ \r\n]"); private static String preprocessBanner(String fileText, ConfigurationFormat format) { Matcher matcher = BANNER_PATTERN.matcher(fileText); if (matcher.find()) { int delimiterIndex = matcher.start(1); char delimiter = fileText.charAt(delimiterIndex); String delimiterText = (delimiter == '^' ? "\\^" : ("[" + Character.toString(delimiter) + "]")); Pattern finalDelimiterPattern = Pattern .compile("(?m)[" + delimiterText + "][\r\n]"); Matcher finalDelimiterMatcher = finalDelimiterPattern .matcher(fileText); if (finalDelimiterMatcher.find(delimiterIndex + 1)) { int finalDelimiterIndex = finalDelimiterMatcher.start(); String beforeDelimiter = fileText.substring(0, delimiterIndex); String betweenDelimiters = fileText.substring(delimiterIndex + 1, finalDelimiterIndex); String afterDelimiter = fileText.substring(finalDelimiterIndex + 1, fileText.length()); String newFileText = beforeDelimiter + "^C" + betweenDelimiters + "^C" + afterDelimiter; return newFileText; } else { throw new BatfishException("Invalid banner"); } } else { return fileText; } } private Path _file; private String _fileText; private ConfigurationFormat _format; private ParseTreeSentences _ptSentences; private Warnings _warnings; public ParseVendorConfigurationJob(Settings settings, String fileText, Path file, Warnings warnings, ConfigurationFormat configurationFormat) { super(settings); _fileText = fileText; _file = file; _ptSentences = new ParseTreeSentences(); _warnings = warnings; _format = configurationFormat; } @Override public ParseVendorConfigurationResult call() throws Exception { long startTime = System.currentTimeMillis(); long elapsedTime; String currentPath = _file.toAbsolutePath().toString(); VendorConfiguration vc = null; BatfishCombinedParser<?, ?> combinedParser = null; ParserRuleContext tree = null; ControlPlaneExtractor extractor = null; ConfigurationFormat format = _format; _logger.info("Processing: '" + currentPath + "'\n"); for (String s : _settings.ignoreFilesWithStrings()) { if (_fileText.contains(s)) { format = ConfigurationFormat.IGNORED; break; } } String relativePathStr = _settings.getActiveTestrigSettings() .getBasePath().relativize(_file).toString(); if (format == ConfigurationFormat.UNKNOWN) { format = VendorConfigurationFormatDetector .identifyConfigurationFormat(_fileText); } switch (format) { case EMPTY: _warnings.redFlag("Empty file: '" + currentPath + "'\n"); elapsedTime = System.currentTimeMillis() - startTime; return new ParseVendorConfigurationResult(elapsedTime, _logger.getHistory(), _file, _warnings, ParseStatus.EMPTY); case IGNORED: _warnings.pedantic("Ignored file: '" + currentPath + "'\n"); elapsedTime = System.currentTimeMillis() - startTime; return new ParseVendorConfigurationResult(elapsedTime, _logger.getHistory(), _file, _warnings, ParseStatus.IGNORED); case ARISTA: case CISCO_IOS: case CISCO_IOS_XR: case CISCO_NX: case FORCE10: case FOUNDRY: String newFileText = _fileText; String fileText; _logger.info("\tPreprocessing..."); do { fileText = newFileText; try { newFileText = preprocessBanner(fileText, format); } catch (BatfishException e) { elapsedTime = System.currentTimeMillis() - startTime; return new ParseVendorConfigurationResult(elapsedTime, _logger.getHistory(), _file, new BatfishException("Error preprocessing banner", e)); } } while (newFileText != fileText); _logger.info("OK\n"); CiscoCombinedParser ciscoParser = new CiscoCombinedParser(newFileText, _settings, format); combinedParser = ciscoParser; extractor = new CiscoControlPlaneExtractor(newFileText, ciscoParser, format, _warnings, _settings.getUnrecognizedAsRedFlag()); break; case HOST: vc = HostConfiguration.fromJson(_fileText, _warnings); elapsedTime = System.currentTimeMillis() - startTime; return new ParseVendorConfigurationResult(elapsedTime, _logger.getHistory(), _file, vc, _warnings, _ptSentences); case VYOS: if (_settings.flattenOnTheFly()) { String msg = "Flattening: '" + currentPath + "' on-the-fly; line-numbers reported for this file will be spurious\n"; _warnings.pedantic(msg); // _logger // .warn("Flattening: \"" // + currentPath // + // "\" on-the-fly; line-numbers reported for this file will be // spurious\n"); _fileText = Batfish.flatten(_fileText, _logger, _settings, ConfigurationFormat.VYOS, VendorConfigurationFormatDetector.BATFISH_FLATTENED_VYOS_HEADER); } else { elapsedTime = System.currentTimeMillis() - startTime; return new ParseVendorConfigurationResult(elapsedTime, _logger.getHistory(), _file, new BatfishException( "Vyos configurations must be flattened prior to this stage: '" + relativePathStr + "'")); } // MISSING BREAK IS INTENTIONAL case FLAT_VYOS: FlatVyosCombinedParser flatVyosParser = new FlatVyosCombinedParser( _fileText, _settings); combinedParser = flatVyosParser; extractor = new FlatVyosControlPlaneExtractor(_fileText, flatVyosParser, _warnings); break; case JUNIPER: if (_settings.flattenOnTheFly()) { String msg = "Flattening: '" + currentPath + "' on-the-fly; line-numbers reported for this file will be spurious\n"; _warnings.pedantic(msg); // _logger // .warn("Flattening: \"" // + currentPath // + // "\" on-the-fly; line-numbers reported for this file will be // spurious\n"); try { _fileText = Batfish.flatten(_fileText, _logger, _settings, ConfigurationFormat.JUNIPER, VendorConfigurationFormatDetector.BATFISH_FLATTENED_JUNIPER_HEADER); } catch (BatfishException e) { String error = "Error flattening configuration file: '" + currentPath + "'"; elapsedTime = System.currentTimeMillis() - startTime; return new ParseVendorConfigurationResult(elapsedTime, _logger.getHistory(), _file, new BatfishException(error, e)); } } else { elapsedTime = System.currentTimeMillis() - startTime; return new ParseVendorConfigurationResult(elapsedTime, _logger.getHistory(), _file, new BatfishException( "Juniper configurations must be flattened prior to this stage: '" + relativePathStr + "'")); } // MISSING BREAK IS INTENTIONAL case FLAT_JUNIPER: FlatJuniperCombinedParser flatJuniperParser = new FlatJuniperCombinedParser( _fileText, _settings); combinedParser = flatJuniperParser; extractor = new FlatJuniperControlPlaneExtractor(_fileText, flatJuniperParser, _warnings); break; case IPTABLES: IptablesCombinedParser iptablesParser = new IptablesCombinedParser( _fileText, _settings); combinedParser = iptablesParser; extractor = new IptablesControlPlaneExtractor(_fileText, iptablesParser, _warnings, relativePathStr); break; case MRV: MrvCombinedParser mrvParser = new MrvCombinedParser(_fileText, _settings); combinedParser = mrvParser; extractor = new MrvControlPlaneExtractor(_fileText, mrvParser, _warnings); break; case ALCATEL_AOS: case AWS_VPC: case BLADENETWORK: case F5: case JUNIPER_SWITCH: case MRV_COMMANDS: case MSS: case VXWORKS: String unsupportedError = "Unsupported configuration format: '" + format.toString() + "' for file: '" + currentPath + "'\n"; if (!_settings.ignoreUnsupported()) { elapsedTime = System.currentTimeMillis() - startTime; return new ParseVendorConfigurationResult(elapsedTime, _logger.getHistory(), _file, new BatfishException(unsupportedError)); } else { _warnings.unimplemented(unsupportedError); elapsedTime = System.currentTimeMillis() - startTime; return new ParseVendorConfigurationResult(elapsedTime, _logger.getHistory(), _file, _warnings, ParseStatus.UNSUPPORTED); } case UNKNOWN: default: String unknownError = "Unknown configuration format for file: '" + currentPath + "'\n"; if (!_settings.ignoreUnknown()) { elapsedTime = System.currentTimeMillis() - startTime; return new ParseVendorConfigurationResult(elapsedTime, _logger.getHistory(), _file, new BatfishException(unknownError)); } else { _warnings.unimplemented(unknownError); elapsedTime = System.currentTimeMillis() - startTime; return new ParseVendorConfigurationResult(elapsedTime, _logger.getHistory(), _file, _warnings, ParseStatus.UNKNOWN); } } try { _logger.info("\tParsing..."); tree = Batfish.parse(combinedParser, _logger, _settings); if (_settings.printParseTree()) { _ptSentences = ParseTreePrettyPrinter.getParseTreeSentences(tree, combinedParser); } _logger.info("\tPost-processing..."); extractor.processParseTree(tree); _logger.info("OK\n"); } catch (ParserBatfishException e) { String error = "Error parsing configuration file: '" + currentPath + "'"; elapsedTime = System.currentTimeMillis() - startTime; return new ParseVendorConfigurationResult(elapsedTime, _logger.getHistory(), _file, new BatfishException(error, e)); } catch (Exception e) { String error = "Error post-processing parse tree of configuration file: '" + currentPath + "'"; elapsedTime = System.currentTimeMillis() - startTime; return new ParseVendorConfigurationResult(elapsedTime, _logger.getHistory(), _file, new BatfishException(error, e)); } finally { Batfish.logWarnings(_logger, _warnings); } vc = extractor.getVendorConfiguration(); vc.setVendor(format); vc.setFilename(_file.getFileName().toString()); // at this point we should have a VendorConfiguration vc String hostname = vc.getHostname(); if (hostname == null) { String error = "No hostname set in file: '" + relativePathStr.replace("\\", "/") + "'\n"; try { _warnings.redFlag(error); } catch (BatfishException e) { elapsedTime = System.currentTimeMillis() - startTime; return new ParseVendorConfigurationResult(elapsedTime, _logger.getHistory(), _file, e); } String filename = _file.getFileName().toString(); String guessedHostname = filename.replaceAll("\\.(cfg|conf)$", ""); _logger.redflag("\tNo hostname set! Guessing hostname from filename: '" + filename + "' ==> '" + guessedHostname + "'\n"); vc.setHostname(guessedHostname); } elapsedTime = System.currentTimeMillis() - startTime; return new ParseVendorConfigurationResult(elapsedTime, _logger.getHistory(), _file, vc, _warnings, _ptSentences); } }