From 4018c7a3693487d21ecbd5a5f00edecc2d2f440f Mon Sep 17 00:00:00 2001 From: Dan Halperin Date: Tue, 15 Jul 2025 15:52:52 -0700 Subject: [PATCH] Remove incorrect \_ \[ \] in posts. Showing up wrong, e.g., here: https://batfish.org/2020/12/18/validating-the-validator.html commit-id:b76fefb5 --- ...entally-break-internet-like-level-three.md | 8 ++-- .../2019-06-14-announcing-batfish-ansible.md | 6 +-- ...oyment-validation-of-bgp-route-policies.md | 40 +++++++++---------- ...s-to-break-a-network-and-one-to-save-it.md | 2 +- _posts/2020-12-18-validating-the-validator.md | 6 +-- ...mating-the-long-pole-of-network-changes.md | 10 ++--- ...ing-the-loop-on-testing-network-changes.md | 12 +++--- .../2021-06-16-the-networking-test-pyramid.md | 2 +- 8 files changed, 43 insertions(+), 43 deletions(-) diff --git a/_posts/2017-11-27-dont-accidentally-break-internet-like-level-three.md b/_posts/2017-11-27-dont-accidentally-break-internet-like-level-three.md index d445e19b..2df4c988 100644 --- a/_posts/2017-11-27-dont-accidentally-break-internet-like-level-three.md +++ b/_posts/2017-11-27-dont-accidentally-break-internet-like-level-three.md @@ -51,9 +51,9 @@ To detect the error before deploying the buggy configuration to the network, the ``` batfish> get bgpAdvertisements differential=true - + EBGP\_SENT dstIp:192.168.51.2 **srcNode:as65000\_R1** srcIp:192.168.51.1 **net:10.1.1.0/24** nhip:192.168.51.1 origin:INCOMPLETE asPath:\[65000, 65001\] communities:\[4259840002, 4259840010\] orIp:192.168.51.1 + + EBGP_SENT dstIp:192.168.51.2 **srcNode:as65000_R1** srcIp:192.168.51.1 **net:10.1.1.0/24** nhip:192.168.51.1 origin:INCOMPLETE asPath:[65000, 65001] communities:[4259840002, 4259840010] orIp:192.168.51.1 - + EBGP\_SENT dstIp:192.168.51.2 **srcNode:as65000\_R1** srcIp:192.168.51.1 **net:10.1.2.0/24** nhip:192.168.51.1 origin:INCOMPLETE asPath:\[65000, 65001\] communities:\[4259840002, 4259840010\] orIp:192.168.51.1 + + EBGP_SENT dstIp:192.168.51.2 **srcNode:as65000_R1** srcIp:192.168.51.1 **net:10.1.2.0/24** nhip:192.168.51.1 origin:INCOMPLETE asPath:[65000, 65001] communities:[4259840002, 4259840010] orIp:192.168.51.1 ``` The output shows that **R1** announces two /24 routes only in the second configuration (indicated by ‘+’), a red flag if the change was never intended to leak such routes. Had it shown no routes, or only routes expected due to the change, the configuration can be deemed safe to deploy (assuming other correctness checks pass too). @@ -61,9 +61,9 @@ The output shows that **R1** announces two /24 routes only in the second config - To confirm if these additional prefixes are part of a less specific route that is already being advertised, one can run the following command ``` - batfish> get bgpAdvertisements prefixSpace=\["10.1.1.0/24:0-23"\] + batfish> get bgpAdvertisements prefixSpace=["10.1.1.0/24:0-23"] - EBGP\_SENT dstIp:192.168.51.2 **srcNode:as65000\_R1** srcIp:192.168.51.1 **net:10.1.0.0/16** nhip:192.168.51.1 origin:INCOMPLETE asPath:\[65000, 65001\] communities:\[4259840002\] orIp:192.168.51.1 + EBGP_SENT dstIp:192.168.51.2 **srcNode:as65000_R1** srcIp:192.168.51.1 **net:10.1.0.0/16** nhip:192.168.51.1 origin:INCOMPLETE asPath:[65000, 65001] communities:[4259840002] orIp:192.168.51.1 ``` The output shows that there is indeed a less specific route (10.1.0.0/16) covering the more specifics. diff --git a/_posts/2019-06-14-announcing-batfish-ansible.md b/_posts/2019-06-14-announcing-batfish-ansible.md index c4b8cfc3..a0ead5df 100644 --- a/_posts/2019-06-14-announcing-batfish-ansible.md +++ b/_posts/2019-06-14-announcing-batfish-ansible.md @@ -31,7 +31,7 @@ To extract “facts” (config settings) from configuration files, one can simpl - name: Setup connection to Batfish service bf_session: host: localhost - name: local\_batfish + name: local_batfish - name: Initialize the example network bf_init_snapshot: @@ -108,7 +108,7 @@ Those advantages aside, the real power of Batfish is in being able to _validate_ ## Use case II: Fact validation -Validating that facts in device configs match what is expected is easy with the  **_bf\_validate\_facts_** module. +Validating that facts in device configs match what is expected is easy with the  **_bf_validate_facts_** module. ``` - name: Validate facts gathered by Batfish @@ -150,7 +150,7 @@ Beyond parsing configs, Batfish builds a full model of device configurations and name: Confirm that there are NO undefined references on any network device ``` -The task above includes four example assertions from our assertion library. The _**bf\_assert**_ module includes more, and based on community feedback, we’ll continue to make more of Batfish’s capabilities available this manner. +The task above includes four example assertions from our assertion library. The _**bf_assert**_ module includes more, and based on community feedback, we’ll continue to make more of Batfish’s capabilities available this manner. Today’s release makes network validating broadly accessible, furthering our commitment to helping network engineers build secure and reliable networks. diff --git a/_posts/2020-10-09-pre-deployment-validation-of-bgp-route-policies.md b/_posts/2020-10-09-pre-deployment-validation-of-bgp-route-policies.md index eff03e13..95aa837a 100644 --- a/_posts/2020-10-09-pre-deployment-validation-of-bgp-route-policies.md +++ b/_posts/2020-10-09-pre-deployment-validation-of-bgp-route-policies.md @@ -51,22 +51,22 @@ The _testRoutePolicies_ question enables you to test the behavior of a route pol For example, to test the "_deny all incoming routes with private addresses_" intent you would run _testRoutePolicies_ on routes with prefixes in the private address space and check that all of them are denied. -Let’s take a look at an example route-policy from\_customer and evaluate its behavior with testRoutePolicies. +Let’s take a look at an example route-policy from_customer and evaluate its behavior with testRoutePolicies. ``` -route-map from\_customer deny 100 +route-map from_customer deny 100 match ip address prefix-list private-ips ! -route-map from\_customer permit 200 +route-map from_customer permit 200 match ip address prefix-list from44 match as-path origin44 set community 20:30 set local-preference 300 ! -route-map from\_customer deny 300 +route-map from_customer deny 300 match ip address prefix-list from44 ! -route-map from\_customer permit 400 +route-map from_customer permit 400 set community 20:30 set local-preference 300 @@ -76,19 +76,19 @@ ip prefix-list private-ips seq 15 permit 192.168.0.0/16 ! ip prefix-list from44 seq 10 permit 5.5.5.0/24 ge 24 ! -ip as-path access-list origin44 permit \_44$ +ip as-path access-list origin44 permit _44$ ``` ``` inRoute1 = BgpRoute(network="10.0.0.0/24", originatorIp="4.4.4.4", originType="egp",protocol="bgp") -result = bfq.testRoutePolicies(policies="from\_customer",direction="in", - inputRoutes=\[inRoute1\]).answer().frame() +result = bfq.testRoutePolicies(policies="from_customer",direction="in", + inputRoutes=[inRoute1]).answer().frame() print(result) - Node    Policy\_Name    Input\_Route Action Output\_Route Difference + Node    Policy_Name    Input_Route Action Output_Route Difference -0  border1  from\_customer  BgpRoute(network='10.0.0.0/24', originatorIp='4.4.4.4', originType='egp', protocol='bgp', asPath=\[\], communities=\[\], localPreference=0, metric=0, sourceProtocol=None)  DENY   None         None -1  border2  from\_customer  BgpRoute(network='10.0.0.0/24', originatorIp='4.4.4.4', originType='egp', protocol='bgp', asPath=\[\], communities=\[\], localPreference=0, metric=0, sourceProtocol=None)  DENY   None         None +0  border1  from_customer  BgpRoute(network='10.0.0.0/24', originatorIp='4.4.4.4', originType='egp', protocol='bgp', asPath=[], communities=[], localPreference=0, metric=0, sourceProtocol=None)  DENY   None         None +1  border2  from_customer  BgpRoute(network='10.0.0.0/24', originatorIp='4.4.4.4', originType='egp', protocol='bgp', asPath=[], communities=[], localPreference=0, metric=0, sourceProtocol=None)  DENY   None         None ``` As you can see, Batfish correctly determines that the 10.0.0.0/24 route advertisement will get denied by the policy. @@ -105,28 +105,28 @@ For example, to verify the "_deny all incoming routes with private addresse_s" i ``` \# Define the space of private addresses and route announcements -privateIps = \["10.0.0.0/8:8-32", "172.16.0.0/28:28-32", "192.168.0.0/16:16-32"\] +privateIps = ["10.0.0.0/8:8-32", "172.16.0.0/28:28-32", "192.168.0.0/16:16-32"] inRoutes1 = BgpRouteConstraints(prefix=privateIps) \# Verify that no such announcement is permitted by our policy -result = bfq.searchRoutePolicies(policies="from\_customer", +result = bfq.searchRoutePolicies(policies="from_customer", inputConstraints=inRoutes1, action="permit").answer().frame() -print(result.loc\[0\]) +print(result.loc[0]) Node border2 -Policy\_Name from\_customer -Input\_Route BgpRoute(network='192.168.0.0/32', originatorIp='0.0.0.0', originType='igp', protocol='bgp', asPath=\[\], communities=\[\], localPreference=0, metric=0, sourceProtocol=None) +Policy_Name from_customer +Input_Route BgpRoute(network='192.168.0.0/32', originatorIp='0.0.0.0', originType='igp', protocol='bgp', asPath=[], communities=[], localPreference=0, metric=0, sourceProtocol=None) Action PERMIT -Output\_Route BgpRoute(network='192.168.0.0/32', originatorIp='0.0.0.0', originType='igp', protocol='bgp', asPath=\[\], communities=\['20:30'\], localPreference=300, metric=0, sourceProtocol=None) -Difference BgpRouteDiffs(diffs=\[BgpRouteDiff(fieldName='communities', oldValue='\[\]', newValue='\[20:30\]'), BgpRouteDiff(fieldName='localPreference', oldValue='0', newValue='300')\]) +Output_Route BgpRoute(network='192.168.0.0/32', originatorIp='0.0.0.0', originType='igp', protocol='bgp', asPath=[], communities=['20:30'], localPreference=300, metric=0, sourceProtocol=None) +Difference BgpRouteDiffs(diffs=[BgpRouteDiff(fieldName='communities', oldValue='[]', newValue='[20:30]'), BgpRouteDiff(fieldName='localPreference', oldValue='0', newValue='300')]) ``` -Batfish has found a route advertisement 192.168.0.0/32 that will be allowed by the routing policy, despite our intent being for it to be denied. There may be multiple route advertisements that violate our intent, Batfish picks one as an example to highlight the error. If you look closely at the routing policy, the route-map from\_customer is going to deny routes that match the prefix-list private-ips. The last entry in that prefix-list is incorrect. It is missing the "ge 16" option. As defined, that entry only matches the exact route 192.168.0.0/16, which means any other prefix from that 192.168.0.0/16 space will not be matched and therefore not be denied by the route-map. +Batfish has found a route advertisement 192.168.0.0/32 that will be allowed by the routing policy, despite our intent being for it to be denied. There may be multiple route advertisements that violate our intent, Batfish picks one as an example to highlight the error. If you look closely at the routing policy, the route-map from_customer is going to deny routes that match the prefix-list private-ips. The last entry in that prefix-list is incorrect. It is missing the "ge 16" option. As defined, that entry only matches the exact route 192.168.0.0/16, which means any other prefix from that 192.168.0.0/16 space will not be matched and therefore not be denied by the route-map. ``` -route-map from\_customer deny 100 match ip address prefix-list private-ips +route-map from_customer deny 100 match ip address prefix-list private-ips ip prefix-list private-ips seq 5 permit 10.0.0.0/8 ge 8 ip prefix-list private-ips seq 10 permit 172.16.0.0/28 ge 28 ip prefix-list private-ips seq 15 permit 192.168.0.0/16 diff --git a/_posts/2020-10-16-three-ways-to-break-a-network-and-one-to-save-it.md b/_posts/2020-10-16-three-ways-to-break-a-network-and-one-to-save-it.md index d16f11e3..e4d75406 100644 --- a/_posts/2020-10-16-three-ways-to-break-a-network-and-one-to-save-it.md +++ b/_posts/2020-10-16-three-ways-to-break-a-network-and-one-to-save-it.md @@ -14,7 +14,7 @@ This category of bugs is as old as typewriters. Some entertaining examples off t - Incorrectly assigned address (a public IP!) to a router interface: _100.x.y.z_ instead of _10.x.y.z_ - Mistyped prefix list name: **prefix-list PFX-LIST 10.1O0.10.128/25** (the letter ‘O’ instead of the number ‘0’ in the prefix) -- Mistyped access-list for a SNMP community: _SNMP-ACCESS-LIST_ instead of _SNMP\_ACCESS\_LIST_ (‘-’ vs ‘\_’) +- Mistyped access-list for a SNMP community: _SNMP-ACCESS-LIST_ instead of _SNMP_ACCESS_LIST_ (‘-’ vs ‘\_’) - Mistyped route-map name: _BLEAD-TRAFFIC_ instead of _BLEED-TRAFFIC_ - Mistyped keyword: **no bgp defaults ipv4-unicast** instead of **no bgp default ipv4-unicast** - Wrong BGP neighbor IP: **neighbor 169.254.127.3 activate** instead of **neighbor 169.54.127.1 activate** diff --git a/_posts/2020-12-18-validating-the-validator.md b/_posts/2020-12-18-validating-the-validator.md index 7669a2a0..18849c63 100644 --- a/_posts/2020-12-18-validating-the-validator.md +++ b/_posts/2020-12-18-validating-the-validator.md @@ -25,16 +25,16 @@ To appreciate the need to go beyond RFCs and docs, consider the following FRR co 2 ip community-list 14 permit 65001:4 3 ip community-list 24 permit 65002:4 4 ! -5 route-map com\_update permit 10 +5 route-map com_update permit 10 6 match community 14 7 on-match goto 20 8 set community 65002:4 additive 9 ! -10 route-map com\_update permit 20 +10 route-map com_update permit 20 11 match community 65002:4 12 set community 65002:5 additive 13 ! -14 route-map com\_update permit 30 +14 route-map com_update permit 30 15 match community 24 16 set community 65002:6 additive 17 ! diff --git a/_posts/2021-05-18-automating-the-long-pole-of-network-changes.md b/_posts/2021-05-18-automating-the-long-pole-of-network-changes.md index 0445bf92..d0047c81 100644 --- a/_posts/2021-05-18-automating-the-long-pole-of-network-changes.md +++ b/_posts/2021-05-18-automating-the-long-pole-of-network-changes.md @@ -39,10 +39,10 @@ Let us illustrate how they work via an example: Allowing access to a new service Your change generation script will use the request parameters to generate the configuration commands for one or more devices. For example, it may generate the following change to the Palo Alto firewall at the edge of the network: -set service S\_TCP\_80 protocol tcp port 80 -set service-group SG\_NEWSERVICE members S\_TCP\_80 -set service S\_TCP\_8080 protocol tcp port 8080 -set service-group SG\_NEWSERVICE members S\_TCP\_8080 +set service S_TCP_80 protocol tcp port 80 +set service-group SG_NEWSERVICE members S_TCP_80 +set service S_TCP_8080 protocol tcp port 8080 +set service-group SG_NEWSERVICE members S_TCP_8080 set address tkt123-dst1 ip-netmask 10.100.40.0/24 set address-group tkt123-dst static tkt123-dst1 @@ -54,7 +54,7 @@ set rulebase security rules tkt123 to INSIDE set rulebase security rules tkt123 source any set rulebase security rules tkt123 destination tkt123-dst set rulebase security rules tkt123 application any -set rulebase security rules tkt123 service SG\_NEWSERVICE +set rulebase security rules tkt123 service SG_NEWSERVICE set rulebase security rules tkt123 action allow This change may be generated using Jinja2 templates, an internal source-of-truth like Netbox, or the Palo Alto Ansible module. Regardless of how it is generated, you can submit it to Batfish Enterprise and analyze it using three criteria. diff --git a/_posts/2021-05-26-closing-the-loop-on-testing-network-changes.md b/_posts/2021-05-26-closing-the-loop-on-testing-network-changes.md index b184285d..faa4015b 100644 --- a/_posts/2021-05-26-closing-the-loop-on-testing-network-changes.md +++ b/_posts/2021-05-26-closing-the-loop-on-testing-network-changes.md @@ -75,7 +75,7 @@ The **.answer().frame()** part transforms the information returned by the questi Suzieq’s Python interface is defined [here](https://suzieq.readthedocs.io/en/latest/developer/pythonAPI/). Suzieq organizes information in tables. For example, you can get the BGP table via: -bgp\_tbl = get\_sqobject(‘bgp’) +bgp_tbl = get_sqobject('bgp') Every table contains a set of functions that return a Pandas DataFrame. Two common functions are get() and aver() (because assert is a Python keyword, Suzieq uses aver, an old synonym). Because Suzieq analyzes the operational state of the network, you must first gather this state by running the Suzieq poller for the devices of interest. [These instructions](https://suzieq.readthedocs.io/en/latest/poller/) will help you start the poller on your network. @@ -91,7 +91,7 @@ You can write Python programs that use the Batfish API to automate your pre-appr ![](/assets/images/fig-2.png){:width="800px"} -This program initializes the network snapshot (with planned config modifications) in **init\_bf()** and defines two tests. **test\_bgp\_status()** uses the **bgpSessionStatus** question to validate that all BGP sessions will be established after the change. **test\_all\_svi\_prefixes\_are\_on\_all\_leafs()** verifies that the SVI prefixes will be reachable on all nodes. It uses the **interfaceProperties** question to retrieve all SVI prefixes and verifies that each is reachable on all nodes. You retrieve the list of nodes using the **nodeProperties** question +This program initializes the network snapshot (with planned config modifications) in **init_bf()** and defines two tests. **test_bgp_status()** uses the **bgpSessionStatus** question to validate that all BGP sessions will be established after the change. **test_all_svi_prefixes_are_on_all_leafs()** verifies that the SVI prefixes will be reachable on all nodes. It uses the **interfaceProperties** question to retrieve all SVI prefixes and verifies that each is reachable on all nodes. You retrieve the list of nodes using the **nodeProperties** question **TIP:** The first time you use Batfish on your network, take a look at the output of **bfq.initIssues().answer().frame()** to confirm that Batfish understands it well. The output of this command is also a good thing to check when a test fails because problems such as syntax errors are also reported in it. @@ -99,11 +99,11 @@ Hopefully, you now see the power of automated testing with tools like Batfish an You can even use pytest, the Python testing framework, to run the tests and make full use of an advanced testing framework. If any of the assertions fail, pytest will report them, and you can investigate the error, fix the config change, and rerun the test suite. -Good testing tools also make it easy to debug test failures. How you do that depends on the test. For example, if we had assigned an incorrect interface IP address on the new leaf, **test\_bgp\_status()** would fail because not all sessions would be in **ESTABLISHED** state. You may then look at the output of **bgpSessionStatus** question, which for this example will show that the sessions on leaf03 and spine01 are incompatible. To understand why, you may then run the **bgpSessionCompatibility** question as follows. +Good testing tools also make it easy to debug test failures. How you do that depends on the test. For example, if we had assigned an incorrect interface IP address on the new leaf, **test_bgp_status()** would fail because not all sessions would be in **ESTABLISHED** state. You may then look at the output of **bgpSessionStatus** question, which for this example will show that the sessions on leaf03 and spine01 are incompatible. To understand why, you may then run the **bgpSessionCompatibility** question as follows. ![](/assets/images/fig-3.png){:width="1000px"} -This output tells you that you likely have the wrong IP address on leaf03 (**NO\_LOCAL\_IP**), and that spine01 expects to establish a session to 10.127.0.5 but no such IP is present in the snapshot (**UNKNOWN\_REMOTE**). If you fix the configs, and rerun the tests,  they should all pass now, and you can be confident that your change is ready to be scheduled for deployment. +This output tells you that you likely have the wrong IP address on leaf03 (**NO_LOCAL_IP**), and that spine01 expects to establish a session to 10.127.0.5 but no such IP is present in the snapshot (**UNKNOWN_REMOTE**). If you fix the configs, and rerun the tests,  they should all pass now, and you can be confident that your change is ready to be scheduled for deployment. #### Deployment pre-testing @@ -117,9 +117,9 @@ As in the case of Batfish, your automated test suite will be a Python program. T ![](/assets/images/fig-4.png){:width="800px"} -Each test uses **get\_sqobject()** to get the relevant tables, then uses the get function to retrieve the rows and columns of interest, and finally checks that a specific column has an expected value on all nodes. The **.all()** checks that the field has that value on all rows of the retrieved dataset. Thus, the test to check that all spines are alive uses the “device” table to retrieve information about the spines, and checks that the “status” column has the value “alive” in all rows. **test\_spine\_port\_is\_free()** assumes that the spine ports have been cabled up and uses the lack of an LLDP peer to confirm that the port connecting to the new leaf is unused +Each test uses **get_sqobject()** to get the relevant tables, then uses the get function to retrieve the rows and columns of interest, and finally checks that a specific column has an expected value on all nodes. The **.all()** checks that the field has that value on all rows of the retrieved dataset. Thus, the test to check that all spines are alive uses the “device” table to retrieve information about the spines, and checks that the “status” column has the value “alive” in all rows. **test_spine_port_is_free()** assumes that the spine ports have been cabled up and uses the lack of an LLDP peer to confirm that the port connecting to the new leaf is unused -Like Batfish, this code is vendor-agnostic and works for any Suzieq-supported vendor. If you add additional leafs, you just need to change the values of SPINE\_IFNAME and NEW\_SVI\_PREFIX. This is the power of writing tests using frameworks like Suzieq. +Like Batfish, this code is vendor-agnostic and works for any Suzieq-supported vendor. If you add additional leafs, you just need to change the values of SPINE_IFNAME and NEW_SVI_PREFIX. This is the power of writing tests using frameworks like Suzieq. If all deployment pre-tests pass, you can confidently deploy the change. But before you declare victory, you still need to test that the deployment went as planned. So, let's do that next. diff --git a/_posts/2021-06-16-the-networking-test-pyramid.md b/_posts/2021-06-16-the-networking-test-pyramid.md index 3a9784d0..497f7f4d 100644 --- a/_posts/2021-06-16-the-networking-test-pyramid.md +++ b/_posts/2021-06-16-the-networking-test-pyramid.md @@ -71,7 +71,7 @@ Pybatfish implementations of all these tests is [here](https://github.com/intent ![](/assets/images/Screen-Shot-2021-06-06-at-8.02.00-PM.png){:width="800px"} -Line 96 uses Batfish’s [bgpProcessConfiguration](https://pybatfish.readthedocs.io/en/latest/notebooks/configProperties.html#BGP-Process-Configuration) question to extract information about all BGP processes on leaf nodes. This information is returned as a Pandas DataFrame—Pandas is a popular data analysis framework and a DataFrame is a tabular representation of the data—in which rows correspond to BGP processes and columns to different settings of the process.  Line 97 extracts BGP processes for which **Multipath\_EBGP** is False. Finally, Line 98 checks that no such processes were found. +Line 96 uses Batfish’s [bgpProcessConfiguration](https://pybatfish.readthedocs.io/en/latest/notebooks/configProperties.html#BGP-Process-Configuration) question to extract information about all BGP processes on leaf nodes. This information is returned as a Pandas DataFrame—Pandas is a popular data analysis framework and a DataFrame is a tabular representation of the data—in which rows correspond to BGP processes and columns to different settings of the process.  Line 97 extracts BGP processes for which **Multipath_EBGP** is False. Finally, Line 98 checks that no such processes were found. We see that tests take only a few lines of Python, and nowhere did we need to account for vendor-specific syntax or defaults. This simplicity stems from the structured, vendor-neutral data model that Batfish builds from device configs.