Filtering BGP Routes Based on AS Paths
Problem
You want to filter the BGP routes that you either send or receive based on AS Path information.
Solution
You can use AS Path filters, either inbound or outbound, to filter either the routes you send or the routes you receive, respectively. You must apply these filters to each peer separately:
Router1#configure terminal
Enter configuration commands, one per line. End with CNTL/Z.
Router1(config)#ip as-path access-list 15 permit ^65501$
Router1(config)#ip as-path access-list 25 permit _65530_
Router1(config)#ip as-path access-list 25 deny _65531$
Router1(config)#ip as-path access-list 25 permit .*
Router1(config)#router bgp 65500
Router1(config-router)#neighbor 192.168.1.5 remote-as 65510
Router1(config-router)#neighbor 192.168.1.5 filter-list 15 in
Router1(config-router)#neighbor 192.168.2.5 remote-as 65520
Router1(config-router)#neighbor 192.168.2.5 filter-list 25 out
Router1(config-router)#exit
Router1(config)#end
Router1#
Discussion
One of the most common reasons for filtering routes based on the AS Path is to prevent AS transit, as we showed in Recipes 9.4 and 9.5. However, there are some other useful applications for AS Path filters. The example shown above contains two distinct filters, one of which applies to routes received inbound from one neighbor, and the other works on outbound routes sent to a second neighbor.
AS Path filters are constructed by using a subset of UNIX regular expressions. Regular expressions provide an extremely powerful and general pattern matching syntax. Many scripting languages, such as Perl, Java, awk, sed, PHP, and Python, use regular expressions for string manipulation. A detailed description of the syntax is out of the scope of this book, but fortunately, BGP path filters don't require all of the magic of the regular expression syntax. This is because all AS Paths consist of simply numbers separated by whitespace. There are no other characters to worry about, and every AS Path has a similar construction. Only the specific ASNs and the number of whitespaces ever change. For more information on regular expressions in general, please refer to Mastering Regular Expressions by Jeffrey Friedl (O'Reilly).
In Recipe 9.4, we showed a simple AS Path filter, which used the pattern ^$. In a regular expression, the symbol ^ stands for the start, and $ for the end of the field. So the pattern ^$simply means that the field is empty because the start is immediately followed by the end. In the case of a BGP AS Path, that means that this route must originate inside this AS.
Looking at the example above, then, it should be clear that access-list number 15 looks for paths that contain only one ASN, which must be 65501:
Router1(config)#ip as-path access-list 15 permit ^65501$
Because there is both a ^ and a $ in the pattern, this filter will match routes whose AS Path consists of just a single ASN, which must have a value of 65501. This filter will remove any downstream routes that AS 65501 is merely passing along. Also, as with normal access lists, AS Path filters end with an implicit deny all clause. So the router will suppress any other routes that don't match this pattern.
The second AS Path filter in the example is somewhat more complicated:
Router1(config)#ip as-path access-list 25 permit _65530_
Router1(config)#ip as-path access-list 25 deny _65531$
Router1(config)#ip as-path access-list 25 permit .*
This shows that you can have filters that span multiple lines, although the example itself is a little bit artificial. The first line in this filter permits any routes that pass through AS 65530. The ASN in this line is surrounded by _ characters. The _ character stands for whitespace, although it is a little bit confusing because, for example, _65530_ seems to imply that it will match the ASN 65530 only if it appears in the middle of an AS Path. But, in fact, _65530_ will match any path containing the ASN, 65530, even if it is at the beginning or the end of the path. Conversely, _65531$ will only match AS Paths that end with AS 65531, meaning those routes that originate in AS 65531.
This little _ delimiter character is extremely important because AS Path filters use a literal text pattern matching. For example, consider the following filter, which doesn't include this character:
Router1(config)#ip as-path access-list 26 permit 55
This AS Path filter will match not only paths containing AS 55, but any other ASN that happens to contain the digits 55, such as 65530, 7553, or 255. But it is unlikely that you actually want to match on substrings within an ASN like this. So you should always remember to include these delimiter characters.
We included the following line in the recipe example because we needed to counteract the implicit deny all at the end of any AS Path access list:
Router1(config)#ip as-path access-list 25 permit .*
This statement explicitly permits all other AS Paths that have not matched any of the earlier lines in the filter rule. The character "." in this filter matches any character, while the * indicates that there can be any number of characters. In fact, * literally means zero or more matches. In many cases, you actually need to match one or more times, for which you can use the + character.
There are many interesting uses for AS Path filters. For example, you might want to allow routes from an ISP and its immediate customers, but not from anything further away. This is easily accomplished with the following filter:
Router1(config)#ip as-path access-list 27 permit ^[0-9]+$
Router1(config)#ip as-path access-list 27 permit ^[0-9]+_[0-9]+$
This filter uses a couple of little tricks. The first trick is to specify a range, as in [0-9]. This means that the rule will match any character that falls in the range from 0 to 9, inclusive. Following this with the + character means that the rule matches one or more of these patterns. So the first line in this filter matches all paths that contain one and only one ASN, although it doesn't matter what this ASN actually is. The second line similarly matches all paths that contain exactly two ASNs. The net effect is to allow only routes from the directly attached ISP AS, and from any other AS that is directly connected to the ISP.
Another way to write the same thing is to match on the delimiters in the AS Path, instead of the actual ASN values. To do this, you might use a pattern like this:
Router1(config)#ip as-path access-list 28 deny _.+_.+_.+_
Router1(config)#ip as-path access-list 28 permit .*
In the first line of this access list, the "." character matches anything, including delimiters as well as digits. So this pattern will match an AS Path that includes at least four AS Path delimiters, with something in between them. Since the first and last delimiters could be the beginning and end of the AS Path, rather than actual whitespace, this access list causes the router to suppress any AS Path that includes three or more ASNs. It's slightly confusing because you have to think in terms of matching on delimiters rather than ASNs, but the net effect of AS Path access list number 28 is identical to 27 above. And, if you wanted to increase the maximum number of ASN values in the path from two to, say, five, this syntax is much more flexible:
Router1(config)#ip as-path access-list 29 deny _.+_.+_.+_.+_.+_.+_
Router1(config)#ip as-path access-list 29 permit .*
It's useful to remember that you can affect not only the routes you receive, but also the routes that you send using AS Path filters. In Recipe 9.4, we showed an extremely useful technique that uses AS Path filters to prevent an AS from being used for transit between external networks:
Router1(config)#ip as-path access-list 15 permit ^$
Router1(config)#router bgp 65500
Router1(config-router)#neighbor 192.168.1.5 remote-as 65520
Router1(config-router)#neighbor 192.168.1.5 filter-list 15 out
In this case, the filter permits only routes that have an empty AS Path, meaning that the routes must have originated locally within this AS. This filter suppresses any external routing information when forwarding its routing table. So the external networks don't know about any downstream networks that can be reached through this router.
You could use a slightly more complicated outbound filter if you wanted. This example allows only directly connected networks to use your AS for transit:
Router1(config)#ip as-path access-list 16 deny _.+_.+_
Router1(config)#ip as-path access-list 16 permit .*
Router1(config)#router bgp 65500
Router1(config-router)#neighbor 192.168.1.5 remote-as 65520
Router1(config-router)#neighbor 192.168.1.5 filter-list 16 out
The router applies this filter before it adds itself to the AS Path. So when we deny the pattern _.+_.+_, this suppresses all AS Paths with two or more ASNs, leaving only AS Paths that have a single ASN. Any path with one ASN must originate in a directly connected AS.
This AS Path filter might seem a little bit confusing because it denies paths that we don't want rather than permitting the ones we do. If you prefer, you could create a filter that has the identical effect by explicitly permitting only the paths that we want and implicity denying the ones we don't want:
Router1(config)#ip as-path access-list 17 permit ^[0-9]+$
Router1(config)#ip as-path access-list 17 permit ^$
Both of these filters allow the router to forward routing information that originates in this AS, and in any networks that are directly connected to us. Bear in mind that this doesn't prevent a device that is fifteen hops away from reaching one of our neighbors through our network. But it does prevent them from reaching anything more distant than one of our direct neighbors through our AS.