Skip to content

BGP Route Filtering and Communities

This guide covers RouterOS 7 BGP routing policy: writing routing filters to control which prefixes are accepted and advertised, manipulating BGP attributes (local-pref, MED, communities), and building practical policies for ISP and enterprise networks.

For BGP session setup, see BGP Peering: eBGP and iBGP Configuration. For the complete filter language reference, see Route Selection and Filters.


RouterOS 7 routing filters use a script-like if/then/else syntax. Each rule is a single rule= string attached to a named chain:

/routing filter rule
add chain=bgp-in rule="if (dst in 192.0.2.0/24) { accept } else { reject }"

Multiple conditions can appear in a single rule:

add chain=bgp-in rule="if (dst in 10.0.0.0/8 && dst-len <= 24) { set bgp-local-pref 200; accept }"

The else branch is optional. Rules without an else clause that do not match fall through to the next rule in the chain. The chain’s default action is reject: any route that reaches the end without an explicit accept is dropped.

A filter chain is a named, ordered list of rules evaluated top-to-bottom. When a rule accepts or rejects a route, evaluation stops. Chains can call other chains with jump and return with return:

# Jump to a shared sanitization chain, then continue in the calling chain
add chain=bgp-in-peer rule="jump bgp-sanitize"
add chain=bgp-in-peer rule="if (dst-len <= 24) { set bgp-local-pref 150; accept }"
add chain=bgp-in-peer rule="reject"
/routing bgp connection
set peer-name input.filter-chain=bgp-in output.filter-chain=bgp-out

input.filter-chain runs on routes received from the peer before they enter the routing table. output.filter-chain runs on routes being advertised to the peer.


Accept or reject a specific prefix:

/routing filter rule
add chain=bgp-out rule="if (dst == 198.51.100.0/24) { accept }"
add chain=bgp-out rule="reject"

The dst-len property matches the prefix length in bits. This is the primary tool for rejecting too-specific routes (host routes, long prefixes) or too-broad aggregates:

# Reject host routes and prefixes longer than /24 on inbound
add chain=bgp-in rule="if (dst-len > 24) { reject }"
# Reject default and prefixes shorter than /8
add chain=bgp-in rule="if (dst-len < 8) { reject }"
# Accept only /8 through /24
add chain=bgp-in rule="if (dst-len >= 8 && dst-len <= 24) { accept }"
add chain=bgp-in rule="reject"

The in operator matches any prefix that falls within a supernet:

# Accept /24s within your allocated /22, reject more-specifics
add chain=bgp-out rule="if (dst in 203.0.113.0/22 && dst-len == 24) { accept }"
add chain=bgp-out rule="reject"

Combined subnet-and-length matching is the correct way to implement IRR or RPKI-style origination checks without a full validation infrastructure.

For larger sets of prefixes, define an address list and reference it in the filter:

# Build a prefix list
/ip firewall address-list
add list=customer-prefixes address=203.0.113.0/24
add list=customer-prefixes address=198.51.100.0/24
# Use it in a filter
/routing filter rule
add chain=bgp-in-customer rule="if (dst in customer-prefixes) { accept }"
add chain=bgp-in-customer rule="reject"

Address lists used in routing filters match on exact prefix, not subnet containment. For subnet range matching, use the in operator with an explicit supernet.


Local Preference (bgp-local-pref) determines the preferred exit from your AS for a given prefix. Higher values are preferred. It is carried only in iBGP and is not sent to eBGP neighbors.

Set it on inbound eBGP filters to control internal path selection:

# Primary upstream: high preference
/routing filter rule
add chain=bgp-in-isp-primary rule="if (dst-len <= 24) { set bgp-local-pref 300; accept }"
add chain=bgp-in-isp-primary rule="reject"
# Secondary upstream: lower preference (used only if primary does not have the route)
add chain=bgp-in-isp-secondary rule="if (dst-len <= 24) { set bgp-local-pref 100; accept }"
add chain=bgp-in-isp-secondary rule="reject"
/routing bgp connection
set to-isp-primary input.filter-chain=bgp-in-isp-primary
set to-isp-secondary input.filter-chain=bgp-in-isp-secondary

MED (bgp-med) signals to neighboring ASes which of your entry points to prefer for a given prefix. Lower MED is preferred. MED is only compared between routes received from the same neighboring AS.

Set MED on outbound filters to influence inbound traffic from a single upstream:

# Advertise prefix via primary link with low MED (preferred entry)
add chain=bgp-out-primary rule="if (dst == 198.51.100.0/24) { set bgp-med 10; accept }"
add chain=bgp-out-primary rule="reject"
# Advertise same prefix via secondary link with high MED (backup entry)
add chain=bgp-out-secondary rule="if (dst == 198.51.100.0/24) { set bgp-med 100; accept }"
add chain=bgp-out-secondary rule="reject"

To clear an inherited MED before sending:

add chain=bgp-out rule="if (dst in 198.51.100.0/24) { unset bgp-out-med; accept }"

Prepending your AS number makes a path appear longer and thus less preferred by external networks. Use it as an alternative to MED when your upstream compares routes from multiple ASes:

# Prepend local AS three times on secondary upstream — traffic prefers primary
add chain=bgp-out-secondary rule="if (dst == 198.51.100.0/24) { set bgp-path-prepend 3; accept }"
add chain=bgp-out-secondary rule="reject"

TypeFormatSizeUse
StandardASN:value (e.g., 65000:100)32-bitGeneral policy tagging
Extendedtype:admin:value (e.g., rt:65000:1)64-bitVPN route-target, SoO
LargeASN:L-value:value (e.g., 65000:100:1)96-bit4-byte ASN policies

The set command replaces the entire community attribute. Use it when you want full control over what is attached:

# Set a standard community on all outbound routes
add chain=bgp-out rule="if (dst in 198.51.100.0/24) { set bgp-communities 65000:100; accept }"

The append command adds communities to the existing list without removing current values:

# Mark customer routes with a local tag before sending to transit
add chain=bgp-out-transit rule="if (dst in customer-prefixes) { append bgp-communities 65000:200; accept }"

RouterOS provides several community matching operators:

OperatorMatches when…
== valueRoute has exactly these communities (no more, no less)
any valueRoute has at least one of the listed communities
includes valueRoute has all of the listed communities
subset valueRoute’s communities are a subset of the listed values
# Accept routes tagged with a specific provider community
add chain=bgp-in rule="if (bgp-communities includes 64500:500) { set bgp-local-pref 250; accept }"
# Match routes with any of several communities
add chain=bgp-in rule="if (bgp-communities any 64500:100,64500:200) { accept }"

Strip communities before advertising to prevent policy information from leaking:

# Remove all well-known communities (NO_EXPORT, NO_ADVERTISE, etc.)
add chain=bgp-sanitize-out rule="if (!bgp-communities-empty) { delete bgp-communities wk; accept }"
add chain=bgp-sanitize-out rule="accept"
# Remove all non-well-known communities (private policy tags)
add chain=bgp-sanitize-out rule="if (!bgp-communities-empty) { delete bgp-communities other; accept }"
add chain=bgp-sanitize-out rule="accept"
# Remove communities matching a regular expression (e.g., all from your own AS)
add chain=bgp-sanitize-out rule="if (!bgp-communities-empty) { delete bgp-communities regexp \"65000:.*\"; accept }"
add chain=bgp-sanitize-out rule="accept"

Named community lists simplify filters that reference the same set of communities in multiple rules:

# Define a community list
/routing filter community-list
add list=upstream-blackhole communities=64500:666
# Use it in a filter
/routing filter rule
add chain=bgp-in rule="if (bgp-communities any-list upstream-blackhole) { set bgp-local-pref 1; accept }"

RFC 1997 defines two well-known communities with mandatory behavior across all standards-compliant BGP implementations:

CommunityNumericEffect
NO_EXPORT65535:65281Do not advertise to eBGP peers
NO_ADVERTISE65535:65282Do not advertise to any peer (iBGP or eBGP)
NO_EXPORT_SUBCONFED65535:65283Do not export to sub-confederation eBGP peers
# Tag a more-specific with NO_EXPORT so it stays within your AS
add chain=bgp-out-ibgp rule="if (dst == 198.51.100.128/25) { append bgp-communities 65535:65281; accept }"
# Honor NO_ADVERTISE on inbound: reject before installing in RIB
add chain=bgp-in rule="if (bgp-communities includes 65535:65282) { reject }"

The IANA-assigned blackhole community (65535:666) signals upstream providers to null-route a specific destination. This is used for upstream DDoS mitigation — you advertise a /32 with this community and the upstream drops traffic to it at their edge.

# Advertise a /32 blackhole to upstream for DDoS mitigation
add chain=bgp-out-blackhole rule="if (dst-len == 32 && dst in 198.51.100.0/24) { \
set bgp-communities 65535:666; accept }"
add chain=bgp-out-blackhole rule="reject"
/routing bgp connection
set to-isp output.filter-chain=bgp-out-blackhole

Many providers require a provider-specific community in addition to or instead of 65535:666. Check your provider’s looking glass or NOC documentation for their exact community values.


RouterOS 7 AS-PATH regular expressions operate on whole AS numbers (not character strings). This differs from RouterOS 6 and Cisco behavior.

# Reject routes that transited AS 64496 (route leak prevention)
add chain=bgp-in rule="if (bgp-as-path .64496.) { reject }"
# Accept routes originated only by AS 64500 (no transit)
add chain=bgp-in rule="if (bgp-as-path ^64500\$) { accept }"
# Reject routes with AS-PATH longer than 5 hops
add chain=bgp-in rule="if (bgp-path-len > 5) { reject }"
# Match routes originated by either AS 64500 or AS 64501
add chain=bgp-in rule="if (bgp-as-path ^(64500|64501)\$) { accept }"

Test AS-PATH regular expressions before applying them to production:

/routing filter test-as-path-regexp regexp="^64500\$" as-path="64500"

RouterOS 7 replaces the v6 concept of peer groups with BGP templates. A template is a named object that holds session parameters and filter assignments; peers inherit from it. This allows a single filter chain to be applied to many peers without repeating configuration.

/routing bgp template
add name=ebgp-base as=65000 router-id=198.51.100.1 \
address-families=ip hold-time=1m keepalive-time=20s

Create role-level templates that extend the base and attach filter chains. Peers inherit both the session parameters and the filters:

# Transit upstream: strict inbound, advertise only your prefixes outbound
/routing bgp template
add name=transit template=ebgp-base \
input.filter-chain=bgp-in-transit \
output.filter-chain=bgp-out-own-prefixes
# Customer: accept only their registered prefixes, send default route
add name=customer template=ebgp-base \
input.filter-chain=bgp-in-customer \
output.filter-chain=bgp-out-default-only
# IXP peer: accept /8–/24, AS-path length ≤ 3
add name=ixp-peer template=ebgp-base \
input.filter-chain=bgp-in-ixp \
output.filter-chain=bgp-out-own-prefixes

A peer can override any single field while inheriting everything else from the template. This is the correct place for per-peer customization such as a different max-prefix limit or a peer-specific inbound chain:

/routing bgp template
add name=customer-65100 template=customer \
input.filter-chain=bgp-in-customer-65100
/routing bgp connection
add name=to-65100 remote.address=203.0.113.1 .as=65100 template=customer-65100

Connections reference a template. All filter and session settings are resolved from the template hierarchy at the time of connection establishment:

/routing bgp connection
add name=to-transit1 remote.address=192.0.2.1 .as=64501 template=transit
add name=to-transit2 remote.address=192.0.2.2 .as=64502 template=transit
add name=to-customer-a remote.address=203.0.113.5 .as=65100 template=customer
add name=to-ixp-peer1 remote.address=198.51.100.10 .as=64600 template=ixp-peer

Both transit connections share bgp-in-transit and bgp-out-own-prefixes. Changing either chain updates policy for all peers in that template at once.

The same pattern applies to iBGP. Define an iBGP base template and derive route-reflector-client or full-mesh variants from it:

/routing bgp template
add name=ibgp-base as=65000 router-id=198.51.100.1 address-families=ip
add name=rr-client template=ibgp-base \
route-reflect=yes \
input.filter-chain=bgp-in-ibgp \
output.filter-chain=bgp-out-ibgp
/routing bgp connection
add name=to-rr-client-1 remote.address=10.0.0.1 .as=65000 template=rr-client
add name=to-rr-client-2 remote.address=10.0.0.2 .as=65000 template=rr-client

A customer at AS 65100 has been allocated 203.0.113.0/24. Accept only that prefix and its subnets down to /28; reject everything else. Set local-pref to identify customer-learned routes for internal policy:

/routing filter rule
add chain=bgp-in-customer-65100 rule="if (dst in 203.0.113.0/24 && dst-len <= 28) { \
set bgp-local-pref 200; accept }"
add chain=bgp-in-customer-65100 rule="reject"
/routing bgp connection
set customer-65100 input.filter-chain=bgp-in-customer-65100

Outbound to customer: send only a default route (or a partial table if agreed):

/routing filter rule
add chain=bgp-out-customer-65100 rule="if (dst == 0.0.0.0/0) { accept }"
add chain=bgp-out-customer-65100 rule="reject"
/routing bgp connection
set customer-65100 output.filter-chain=bgp-out-customer-65100

A peer at AS 64500 should only send routes it originates or its customers originate. Reject transit routes (AS-PATH length > 2). Accept /8–/24 only:

/routing filter rule
add chain=bgp-in-peer-64500 rule="if (dst-len < 8 || dst-len > 24) { reject }"
add chain=bgp-in-peer-64500 rule="if (bgp-path-len > 2) { reject }"
add chain=bgp-in-peer-64500 rule="if (bgp-as-path ^64500.*\$) { \
set bgp-local-pref 150; accept }"
add chain=bgp-in-peer-64500 rule="reject"

Outbound to peer: send only your own prefixes, strip internal communities:

/routing filter rule
add chain=bgp-out-peer-64500 rule="if (dst in 198.51.100.0/22 && dst-len >= 22 && dst-len <= 24) { \
delete bgp-communities other; accept }"
add chain=bgp-out-peer-64500 rule="reject"

Inbound from a transit provider: accept full table but reject bogons and too-specific prefixes. Set local-pref lower than peers (transit is last-resort):

/routing filter rule
# Reject RFC 1918 and other bogons
add chain=bgp-in-transit rule="if (dst in 10.0.0.0/8) { reject }"
add chain=bgp-in-transit rule="if (dst in 172.16.0.0/12) { reject }"
add chain=bgp-in-transit rule="if (dst in 192.168.0.0/16) { reject }"
add chain=bgp-in-transit rule="if (dst in 0.0.0.0/8) { reject }"
add chain=bgp-in-transit rule="if (dst in 169.254.0.0/16) { reject }"
add chain=bgp-in-transit rule="if (dst in 240.0.0.0/4) { reject }"
add chain=bgp-in-transit rule="if (dst == 0.0.0.0/0) { reject }"
# Reject too-specific (host routes from global table) and too-broad
add chain=bgp-in-transit rule="if (dst-len < 8 || dst-len > 24) { reject }"
# Accept remainder with transit local-pref
add chain=bgp-in-transit rule="if (dst-len >= 8 && dst-len <= 24) { \
set bgp-local-pref 100; accept }"
add chain=bgp-in-transit rule="reject"

Enterprise: Dual-Homed with Community-Based Failover

Section titled “Enterprise: Dual-Homed with Community-Based Failover”

An enterprise with two ISPs uses communities to control routing. ISP-A’s blackhole community is 64500:666. Mark internal prefixes to influence inbound traffic:

# Inbound from ISP-A: default route only, high preference
/routing filter rule
add chain=bgp-in-ispa rule="if (dst == 0.0.0.0/0) { set bgp-local-pref 200; accept }"
add chain=bgp-in-ispa rule="reject"
# Inbound from ISP-B: default route only, low preference
add chain=bgp-in-ispb rule="if (dst == 0.0.0.0/0) { set bgp-local-pref 100; accept }"
add chain=bgp-in-ispb rule="reject"
# Outbound to ISP-A: advertise prefix, prefer inbound via A (low MED)
add chain=bgp-out-ispa rule="if (dst == 198.51.100.0/24) { set bgp-med 10; accept }"
add chain=bgp-out-ispa rule="reject"
# Outbound to ISP-B: advertise prefix, deprioritize inbound via B (high MED + prepend)
add chain=bgp-out-ispb rule="if (dst == 198.51.100.0/24) { \
set bgp-med 100; set bgp-path-prepend 2; accept }"
add chain=bgp-out-ispb rule="reject"
# Trigger upstream blackhole for a /32 under attack
add chain=bgp-out-blackhole rule="if (dst-len == 32 && dst in 198.51.100.0/24) { \
append bgp-communities 64500:666; accept }"
add chain=bgp-out-blackhole rule="reject"

Stripping Communities at Administrative Boundaries

Section titled “Stripping Communities at Administrative Boundaries”

When a route crosses from your AS to a customer or peer, strip internal policy communities to prevent information leakage:

/routing filter rule
# Remove all internal communities before sending to any eBGP neighbor
add chain=bgp-out-strip rule="if (!bgp-communities-empty) { \
delete bgp-communities regexp \"65000:.*\"; accept }"
add chain=bgp-out-strip rule="accept"
/routing bgp connection
# Apply strip chain for all external connections
set to-customer output.filter-chain=bgp-out-strip
set to-peer output.filter-chain=bgp-out-strip

Omitting a final reject rule. The chain default is reject, but an explicit final reject documents intent and makes audits easier.

Modifying and accepting in the wrong order. Actions in a rule’s block execute left-to-right. set bgp-local-pref 200; accept is correct. accept; set bgp-local-pref 200 applies the accept immediately and the set is unreachable.

Using v6-style AS-path regexp. In RouterOS 7, bgp-as-path .1234. matches ASN 1234 as a whole token, not the substring “1234” in the path string. Patterns copied from v6 or Cisco configs may produce unexpected matches.

Setting local-pref on outbound. Local Preference is only meaningful on inbound routes. Setting it in an output filter has no effect on path selection at the receiving peer.

MED comparison across ASes. MED is only compared by the receiving router between routes from the same neighboring AS. Setting MED to influence traffic from two different ASes requires AS-path prepending instead.


/routing filter rule print where chain=bgp-in

Check What a Peer Is Sending vs What Was Accepted

Section titled “Check What a Peer Is Sending vs What Was Accepted”
# Routes received (pre-filter)
/routing bgp advertisements print where session=peer-name
# Routes installed from BGP
/ip route print where bgp
# BGP RIB with filter state
/routing route print where bgp && !active

Inactive BGP routes in the RIB (flag b but not A) were received and parsed but not installed as best path. This is normal when a better path exists. Routes rejected by input filters do not appear in the RIB at all.

Temporarily assign the chain to a connection and monitor prefix counts:

/routing bgp connection set peer-name input.filter-chain=test-chain
/routing bgp session print detail where name=peer-name
# Check input-prefixes

Remove when done:

/routing bgp connection set peer-name input.filter-chain=bgp-in