<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Virtual Network Routing Appliance on Brewed in the Cloud by Chris Hailes</title><link>https://blog.brewedinthecloud.com/categories/virtual-network-routing-appliance/</link><description>Recent content in Virtual Network Routing Appliance on Brewed in the Cloud by Chris Hailes</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Thu, 02 Jul 2026 00:00:00 +1000</lastBuildDate><atom:link href="https://blog.brewedinthecloud.com/categories/virtual-network-routing-appliance/rss.xml" rel="self" type="application/rss+xml"/><item><title>When Routes Disagree: VNRA Precedence and Conflict Resolution in Azure</title><link>https://blog.brewedinthecloud.com/p/vnra-route-precedence-and-conflict-resolution/</link><pubDate>Thu, 02 Jul 2026 00:00:00 +1000</pubDate><guid>https://blog.brewedinthecloud.com/p/vnra-route-precedence-and-conflict-resolution/</guid><description>&lt;p&gt;If you have ever looked at an Azure effective route table and thought, &lt;em&gt;that should not be the route that wins&lt;/em&gt;, this is the part that matters.&lt;/p&gt;
&lt;p&gt;Route precedence in Azure is often explained as if each routing feature gets a turn at being authoritative. System routes do the baseline work, BGP propagated routes inject dynamic reachability, User Defined Routes (UDRs) override intent, and VNRA somehow arrives as a new control point that sits above the rest.&lt;/p&gt;
&lt;p&gt;That framing is convenient. It is also what causes people to misread packet paths.&lt;/p&gt;
&lt;p&gt;The stronger and more useful position is this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;VNRA is not a separate precedence domain. It changes routing outcomes inside Azure’s existing route selection logic, and the only version that matters is the effective route behaviour seen by the workload NIC.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That changes something you design and operate straight away. You stop asking whether VNRA “wins” against UDRs, BGP, or system routes as if it were a fourth referee. Instead, you ask what prefixes are present, which route source owns the winning prefix, and what next hop is actually selected for the packet.&lt;/p&gt;
&lt;p&gt;That is the difference between platform intent and data-plane truth.&lt;/p&gt;
&lt;h2 id="the-mental-model"&gt;The Mental Model
&lt;/h2&gt;&lt;p&gt;The common assumption is that Azure routing is layered by feature:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;system routes are default behaviour&lt;/li&gt;
&lt;li&gt;propagated routes add learned reachability&lt;/li&gt;
&lt;li&gt;UDRs override platform defaults&lt;/li&gt;
&lt;li&gt;VNRA adds a new routing authority on top&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That model breaks because Azure does not forward packets based on feature hierarchy. It forwards packets based on the effective route selected for a destination from the source NIC.&lt;/p&gt;
&lt;p&gt;VNRA matters, but not because it suspends the normal rules. It matters because it affects the set of candidate routes and next-hop outcomes that Azure can realise. Once those candidates exist, Azure still resolves forwarding the same way it always has:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;longest prefix match first&lt;/li&gt;
&lt;li&gt;route source precedence only when prefixes are equal&lt;/li&gt;
&lt;li&gt;the selected route becomes the packet path&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So the mental shift is simple:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;VNRA should be treated as a routing outcome modifier visible through effective route behaviour, not as a separate precedence domain.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you treat it as a special layer above the rest, you will keep looking for feature interactions when the real answer is usually just prefix specificity plus route source precedence.&lt;/p&gt;
&lt;h2 id="how-it-really-works"&gt;How It Really Works
&lt;/h2&gt;&lt;p&gt;Azure route selection is less mysterious when you strip it back to packet behaviour.&lt;/p&gt;
&lt;p&gt;For any destination, Azure evaluates the effective route set applied to the source interface. The decision process is straightforward:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Find all matching routes for the destination IP&lt;/li&gt;
&lt;li&gt;Select the most specific prefix&lt;/li&gt;
&lt;li&gt;If multiple routes have the same prefix length, apply route source precedence&lt;/li&gt;
&lt;li&gt;Use the winning route’s next hop&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That means the first real question is never &lt;em&gt;which routing feature is in charge&lt;/em&gt;. It is:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Which route actually wins for this prefix on this NIC?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That sounds obvious, but it gets lost quickly once VNRA enters the conversation. Engineers start attributing precedence to the feature rather than to the route that became effective.&lt;/p&gt;
&lt;h3 id="a-data-plane-view-of-route-selection"&gt;A data-plane view of route selection
&lt;/h3&gt;&lt;div class="mermaid"&gt;flowchart TD
A[Packet arrives at source NIC] --&gt; B[Match destination against effective routes]
B --&gt; C{Matching routes exist?}
C --&gt;|No| D[No explicit match beyond platform defaults]
C --&gt;|Yes| E[Select longest prefix match]
E --&gt; F{Equal prefix candidates remain?}
F --&gt;|No| G[Use selected next hop]
F --&gt;|Yes| H[Apply route source precedence]
H --&gt; I[Install winning route as forwarding decision]
I --&gt; G
&lt;/div&gt;
&lt;p&gt;The key point is deliberately plain: VNRA does not replace this logic. It shows up through it.&lt;/p&gt;
&lt;h2 id="route-source-conflicts-in-practice"&gt;Route Source Conflicts in Practice
&lt;/h2&gt;&lt;p&gt;The route sources involved are not new, even if the design looks new.&lt;/p&gt;
&lt;h3 id="system-routes"&gt;System routes
&lt;/h3&gt;&lt;p&gt;System routes are Azure’s built-in opinion about reachability. They remain valid until something more specific exists, or an equal-prefix route with higher precedence takes their place.&lt;/p&gt;
&lt;p&gt;The mistake is to treat them as irrelevant once you start using VNRA. They are still part of the candidate set, and they still win whenever nothing more specific or higher-precedence applies.&lt;/p&gt;
&lt;h3 id="propagated-routes"&gt;Propagated routes
&lt;/h3&gt;&lt;p&gt;BGP propagated routes can override system routes for the same prefix. They do not override a more specific route from another source, and they do not outrank a UDR for an equal prefix.&lt;/p&gt;
&lt;p&gt;This is where people often start blaming VNRA for inconsistency when the real cause is simpler: a propagated route exists for a narrower prefix than the summarised path they expected to carry appliance-bound traffic.&lt;/p&gt;
&lt;h3 id="udrs"&gt;UDRs
&lt;/h3&gt;&lt;p&gt;UDRs are still the clearest way to impose routing intent for a specific prefix. For equal prefixes, a UDR takes precedence over propagated and system routes.&lt;/p&gt;
&lt;p&gt;That does not make UDRs universally dominant. A broad UDR only controls traffic where no more specific route exists. If you define a default route or a large summary and then allow narrower prefixes to appear from another source, Azure will choose the narrower route every time.&lt;/p&gt;
&lt;p&gt;That is not an exception. That is the design consequence of using broad routing intent.&lt;/p&gt;
&lt;h3 id="where-vnra-fits"&gt;Where VNRA fits
&lt;/h3&gt;&lt;p&gt;This is the line to stay disciplined on:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;VNRA does not appear as a separate route-precedence layer that overrides Azure routing rules. Its impact is expressed through the effective routes and next-hop decisions Azure realises.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So when engineers say “VNRA overrode my route”, what they usually mean is one of four things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a more specific route existed than the one they expected to drive traffic through the appliance&lt;/li&gt;
&lt;li&gt;an equal-prefix route with higher precedence was effective&lt;/li&gt;
&lt;li&gt;the route they expected was configured, but not the route selected&lt;/li&gt;
&lt;li&gt;they were reasoning from design intent, not from the effective route table on the workload NIC&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That is not semantics. It is the operational difference between understanding Azure routing and narrating it.&lt;/p&gt;
&lt;h2 id="what-actually-creates-ambiguity"&gt;What Actually Creates Ambiguity
&lt;/h2&gt;&lt;p&gt;Here is the harder opinion this topic needs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;VNRA does not introduce routing ambiguity. Overlapping prefixes and poor route intent do. VNRA just makes that ambiguity harder to ignore.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That matters because a lot of post-deployment confusion gets framed as platform inconsistency when it is really unresolved routing intent:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;broad appliance steering is expected to cover everything&lt;/li&gt;
&lt;li&gt;narrower prefixes are introduced later by propagation or platform behaviour&lt;/li&gt;
&lt;li&gt;traffic follows the narrower route&lt;/li&gt;
&lt;li&gt;the team concludes VNRA is unpredictable&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But the route selection was predictable. The design was not precise enough.&lt;/p&gt;
&lt;p&gt;If you want deterministic packet paths, your prefixes need to be deterministic. VNRA cannot rescue vague routing intent.&lt;/p&gt;
&lt;h2 id="real-world-impact"&gt;Real-World Impact
&lt;/h2&gt;&lt;p&gt;This changes how you assess routing risk.&lt;/p&gt;
&lt;h3 id="reliability"&gt;Reliability
&lt;/h3&gt;&lt;p&gt;A route conflict does not need to cause packet loss to be a problem. If traffic takes a path other than the one assumed by the design, reliability becomes conditional. The system may work until a dependency expects inspection, symmetry, or path locality that is no longer true.&lt;/p&gt;
&lt;h3 id="security"&gt;Security
&lt;/h3&gt;&lt;p&gt;If your security model depends on traffic traversing an appliance path, broad route intent is not enough. Any more specific competing route can bypass that path without creating a visible outage.&lt;/p&gt;
&lt;p&gt;That means inspection assumptions are only as strong as your prefix discipline.&lt;/p&gt;
&lt;h3 id="scalability"&gt;Scalability
&lt;/h3&gt;&lt;p&gt;The larger the environment, the more likely it is that summarised intent and specific reachability will collide. VNRA does not make large environments harder to reason about by itself. It exposes where the routing model was already under-specified.&lt;/p&gt;
&lt;h3 id="operational-clarity"&gt;Operational clarity
&lt;/h3&gt;&lt;p&gt;The practical question remains:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;How would this change something I design, deploy, or operate?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It means you validate routing by effective outcome, not by feature configuration. And it means broad steering constructs should be treated as coarse guidance unless you have actively controlled competing specific prefixes.&lt;/p&gt;
&lt;h2 id="implementation-example"&gt;Implementation Example
&lt;/h2&gt;&lt;p&gt;This post is not a deployment guide, but precedence claims need a concrete artefact. The right artefact here is the effective route table, because that is where Azure shows the realised forwarding candidates.&lt;/p&gt;
&lt;h3 id="azure-cli-inspect-effective-routes-on-a-nic"&gt;Azure CLI: inspect effective routes on a NIC
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;az network nic show-effective-route-table &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="se"&gt;&lt;/span&gt; --resource-group rg-network-prod &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="se"&gt;&lt;/span&gt; --name nic-app-01 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="se"&gt;&lt;/span&gt; --output table
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;For this topic, the fields that matter are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;destination prefix&lt;/li&gt;
&lt;li&gt;route source&lt;/li&gt;
&lt;li&gt;next hop type&lt;/li&gt;
&lt;li&gt;next hop IP, where applicable&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If your routing argument is not visible there, it is not yet a data-plane argument.&lt;/p&gt;
&lt;h3 id="example-conflict"&gt;Example conflict
&lt;/h3&gt;&lt;p&gt;Assume the effective route table contains the following entries:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;Prefix&lt;/th&gt;
&lt;th&gt;Next Hop Type&lt;/th&gt;
&lt;th&gt;Next Hop&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Default&lt;/td&gt;
&lt;td&gt;10.20.0.0/16&lt;/td&gt;
&lt;td&gt;Virtual network&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VirtualNetworkGateway&lt;/td&gt;
&lt;td&gt;10.20.10.0/24&lt;/td&gt;
&lt;td&gt;Virtual network gateway&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User&lt;/td&gt;
&lt;td&gt;10.20.0.0/16&lt;/td&gt;
&lt;td&gt;Virtual appliance&lt;/td&gt;
&lt;td&gt;10.0.1.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User&lt;/td&gt;
&lt;td&gt;0.0.0.0/0&lt;/td&gt;
&lt;td&gt;Virtual appliance&lt;/td&gt;
&lt;td&gt;10.0.1.4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Now apply packet logic.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Traffic to &lt;code&gt;10.20.10.7&lt;/code&gt; follows the &lt;code&gt;/24&lt;/code&gt; propagated route because it is the most specific match&lt;/li&gt;
&lt;li&gt;Traffic to &lt;code&gt;10.20.50.9&lt;/code&gt; follows the &lt;code&gt;/16&lt;/code&gt; UDR because it is an equal-prefix conflict with the system route and the UDR has higher precedence&lt;/li&gt;
&lt;li&gt;Traffic to &lt;code&gt;8.8.8.8&lt;/code&gt; follows the &lt;code&gt;0.0.0.0/0&lt;/code&gt; UDR unless a more specific route exists&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of those outcomes require a special VNRA rule. They only require Azure to apply normal route selection.&lt;/p&gt;
&lt;h2 id="deterministic-vs-non-deterministic-outcomes"&gt;Deterministic vs Non-Deterministic Outcomes
&lt;/h2&gt;&lt;p&gt;This distinction needs to be blunt.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Azure route selection is deterministic once the effective route set is realised. The non-determinism people experience is usually in route convergence, propagation timing, or control-plane state visibility not in the precedence engine itself.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That is an important separation.&lt;/p&gt;
&lt;p&gt;If the same NIC has the same effective routes and the same destination, Azure should produce the same forwarding decision. If the observed behaviour changes over time, the first suspicion should be changing route state, not changing precedence logic.&lt;/p&gt;
&lt;p&gt;In VNRA discussions, that matters because people often attribute temporary inconsistency to the feature when they are really observing asynchronous route realisation.&lt;/p&gt;
&lt;p&gt;That does not make the problem trivial. It just means the problem is different.&lt;/p&gt;
&lt;h2 id="gotchas--edge-cases"&gt;Gotchas &amp;amp; Edge Cases
&lt;/h2&gt;&lt;h3 id="broad-appliance-steering-does-not-guarantee-universal-appliance-transit"&gt;Broad appliance steering does not guarantee universal appliance transit
&lt;/h3&gt;&lt;p&gt;A default route or summary route pointing to an appliance only governs traffic where no narrower match exists. If you need deterministic inspection for a destination set, summarised intent alone is weak control.&lt;/p&gt;
&lt;h3 id="route-presence-is-not-route-selection"&gt;Route presence is not route selection
&lt;/h3&gt;&lt;p&gt;A route being present in the effective route table does not mean it is the route used for your destination. It only means it is a candidate.&lt;/p&gt;
&lt;h3 id="equal-prefix-reasoning-is-often-skipped-too-early"&gt;Equal-prefix reasoning is often skipped too early
&lt;/h3&gt;&lt;p&gt;Engineers jump to route source precedence before checking whether the conflicting routes even have equal prefixes. Most disputes disappear once you compare prefix length honestly.&lt;/p&gt;
&lt;h3 id="vnra-can-be-blamed-for-decisions-it-did-not-make"&gt;VNRA can be blamed for decisions it did not make
&lt;/h3&gt;&lt;p&gt;If the winning path is the result of a more specific propagated route or an equal-prefix UDR, the routing logic is doing exactly what Azure normally does. VNRA may be involved in the design, but not in the precedence mechanism.&lt;/p&gt;
&lt;h2 id="best-practices"&gt;Best Practices
&lt;/h2&gt;&lt;h3 id="design-routing-intent-at-the-prefix-level"&gt;Design routing intent at the prefix level
&lt;/h3&gt;&lt;p&gt;Do not ask whether VNRA should “take precedence”. Ask which exact prefixes must be controlled, and what other sources may advertise or inject overlapping routes.&lt;/p&gt;
&lt;h3 id="treat-broad-routes-as-coarse-policy-not-hard-enforcement"&gt;Treat broad routes as coarse policy, not hard enforcement
&lt;/h3&gt;&lt;p&gt;A summary or default route gives directional intent. It does not prove exclusive path ownership.&lt;/p&gt;
&lt;h3 id="validate-from-the-workload-nic"&gt;Validate from the workload NIC
&lt;/h3&gt;&lt;p&gt;Configured route tables tell you what you meant. Effective routes tell you what Azure is prepared to do. For conflict resolution, only the latter settles the question.&lt;/p&gt;
&lt;h3 id="call-overlapping-prefixes-what-they-are"&gt;Call overlapping prefixes what they are
&lt;/h3&gt;&lt;p&gt;If two routing sources are competing for the same traffic set because the prefix model is sloppy, that is not platform complexity. It is unresolved design intent.&lt;/p&gt;
&lt;div class="insight"&gt;
&lt;div class="insight-icon"&gt;🍺&lt;/div&gt;
&lt;div class="insight-content"&gt;
&lt;strong&gt;Brewed Insight:&lt;/strong&gt; Most “VNRA routing surprises” are not VNRA surprises at all. They are ordinary Azure route selection rules exposing a design that relied on broad intent where specific control was required.
&lt;/div&gt;
&lt;/div&gt;
&lt;style&gt;
.insight {
display: flex;
align-items: center;
background-color: #0089e41c;
border-left: 10px solid #D69A2D;
padding: 10px;
margin: 20px 0;
border-radius: 4px;
}
.insight-icon {
font-size: 24px;
margin-right: 10px;
}
.insight-content {
flex: 1;
}
&lt;/style&gt;&lt;h2 id="learn-more"&gt;Learn More
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://learn.microsoft.com/en-us/azure/virtual-network/virtual-network-routing-appliance-overview" target="_blank" rel="noopener"
&gt;Overview of Azure Virtual Network routing appliances&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://learn.microsoft.com/azure/virtual-network/virtual-networks-udr-overview" target="_blank" rel="noopener"
&gt;Azure virtual network traffic routing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://learn.microsoft.com/azure/virtual-network/diagnose-network-routing-problem" target="_blank" rel="noopener"
&gt;Diagnose a virtual machine routing problem&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://learn.microsoft.com/azure/virtual-network/" target="_blank" rel="noopener"
&gt;Azure Virtual Network documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://learn.microsoft.com/cli/azure/network/nic#az-network-nic-show-effective-route-table" target="_blank" rel="noopener"
&gt;Azure CLI &lt;code&gt;az network nic show-effective-route-table&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>