<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Spain on alikhil</title>
    <link>https://alikhil.dev/tags/spain/</link>
    <description>Recent content in Spain on alikhil</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <managingEditor>Alik Khilazhev</managingEditor>
    <lastBuildDate>Wed, 23 Sep 2026 23:00:00 +0200</lastBuildDate><atom:link href="https://alikhil.dev/tags/spain/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>My MikroTik workaround for LaLiga Cloudflare blocks</title>
      <link>https://alikhil.dev/posts/mikrotik-workaround-laliga-cloudflare-blocks/</link>
      <pubDate>Wed, 23 Sep 2026 23:00:00 +0200</pubDate>
      <author>Alik Khilazhev</author>
      <guid>https://alikhil.dev/posts/mikrotik-workaround-laliga-cloudflare-blocks/</guid>
      <description>I got tired of manually sending Cloudflare traffic through a VPN whenever LaLiga blocks shared IP addresses during a match. So I taught my MikroTik to do it for me.</description>
      <content:encoded><![CDATA[<p>Spain loves football. Spain also has plenty of people watching football on pirate streams. Those streams often sit behind Cloudflare, where one IP address can belong to a lot of completely unrelated websites.</p>
<p>LaLiga spent a while trying to get Cloudflare to take the pirate streams down. That did not work out. Then LaLiga got a <a href="https://ooni.github.io/post/2026-laliga-collateral/">court order</a> that lets Spanish ISPs block IP addresses during matches. So they block the IPs used by the streams, and legitimate sites behind Cloudflare go down with them.</p>
<p><img loading="lazy" src="/posts/mikrotik-workaround-laliga-cloudflare-blocks/images/banner.webp" type="" alt="Message shown by a Spanish ISP when you try to load a website hosted on an IP address it has blocked."  /></p>
<p><em>Message shown by a Spanish ISP when you try to load a website hosted on an IP address it has blocked.</em></p>
<p>The result is annoying. A random site does not load. An API starts timing out. You can spend an hour debugging your own setup before noticing that there is a LaLiga match on.</p>
<p>I used to fix it by enabling a rule on my MikroTik that sends Cloudflare traffic through a VPN. Every match, same small ritual. Recently I found <a href="https://hayahora.futbol/">¿Hay ahora fútbol?</a>, a site with one useful question: is football on right now, and is Cloudflare probably broken again?</p>
<p>It also exposes its signal over DNS. That was enough to automate the whole thing.</p>
<h2 id="what-my-router-changes">What my router changes</h2>
<p>The router keeps a list of Cloudflare IP ranges. When Hayahora reports a large set of blocked IPs, it enables one disabled mangle rule. New connections from my LAN to Cloudflare then go through my existing VPN. When the DNS answer goes back to normal, the rule turns itself off.</p>
<p>Only Cloudflare traffic takes the detour. The VPN is not my default route, and the rest of the network keeps using my normal ISP connection.</p>
<p>My setup uses RouterOS 7 and an existing policy-based IPsec connection marked <code>NordVPN</code>. If your VPN uses a different connection mark, replace <code>NordVPN</code> below. Get the VPN working first; this post only adds the automatic switch.</p>
<h2 id="the-mikrotik-setup">The MikroTik setup</h2>
<p>First, refresh a firewall address list named <code>cloudflare-ips</code> every day. I use <a href="https://github.com/Davie3/mikrotik-cloudflare-iplist">Davie3&rsquo;s list</a>, but any maintained Cloudflare IP-list import will do.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="cl"><span class="o">/</span><span class="n">system</span> <span class="n">script</span>
</span></span><span class="line"><span class="cl"><span class="n">add</span> <span class="n">name</span><span class="o">=</span><span class="n">refresh</span><span class="o">-</span><span class="n">cloudflare</span><span class="o">-</span><span class="n">ips</span> <span class="n">policy</span><span class="o">=</span><span class="n">ftp</span><span class="p">,</span><span class="n">read</span><span class="p">,</span><span class="n">write</span><span class="p">,</span><span class="n">policy</span><span class="p">,</span><span class="n">test</span> <span class="n">source</span><span class="o">=</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="o">/</span><span class="k">tool</span> <span class="n">fetch</span> <span class="n">url</span><span class="o">=</span><span class="s2">&#34;https://raw.githubusercontent.com/Davie3/mikrotik-cloudflare-iplist/main/cloudflare-ips-v4.rsc&#34;</span> \
</span></span><span class="line"><span class="cl">        <span class="n">check</span><span class="o">-</span><span class="n">certificate</span><span class="o">=</span><span class="n">yes</span> <span class="n">dst</span><span class="o">-</span><span class="n">path</span><span class="o">=</span><span class="n">cloudflare</span><span class="o">-</span><span class="n">ips</span><span class="o">-</span><span class="n">v4</span><span class="o">.</span><span class="n">rsc</span>
</span></span><span class="line"><span class="cl">    <span class="o">/</span><span class="n">ip</span> <span class="n">firewall</span> <span class="n">address</span><span class="o">-</span><span class="n">list</span> <span class="n">remove</span> <span class="p">[</span><span class="n">find</span> <span class="n">where</span> <span class="n">list</span><span class="o">=</span><span class="s2">&#34;cloudflare-ips&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="o">/</span><span class="n">import</span> <span class="n">file</span><span class="o">-</span><span class="n">name</span><span class="o">=</span><span class="n">cloudflare</span><span class="o">-</span><span class="n">ips</span><span class="o">-</span><span class="n">v4</span><span class="o">.</span><span class="n">rsc</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">/</span><span class="n">system</span> <span class="n">scheduler</span>
</span></span><span class="line"><span class="cl"><span class="n">add</span> <span class="n">name</span><span class="o">=</span><span class="n">refresh</span><span class="o">-</span><span class="n">cloudflare</span><span class="o">-</span><span class="n">ips</span> <span class="n">interval</span><span class="o">=</span><span class="mi">1</span><span class="n">d</span> <span class="n">start</span><span class="o">-</span><span class="n">time</span><span class="o">=</span><span class="mi">00</span><span class="p">:</span><span class="mi">15</span><span class="p">:</span><span class="mi">00</span> <span class="n">on</span><span class="o">-</span><span class="n">event</span><span class="o">=</span><span class="n">refresh</span><span class="o">-</span><span class="n">cloudflare</span><span class="o">-</span><span class="n">ips</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">/</span><span class="n">system</span> <span class="n">script</span> <span class="n">run</span> <span class="n">refresh</span><span class="o">-</span><span class="n">cloudflare</span><span class="o">-</span><span class="n">ips</span>
</span></span></code></pre></div><p>Then add this rule. It starts disabled on purpose.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">/ip firewall mangle
</span></span><span class="line"><span class="cl">add chain=prerouting action=mark-connection connection-mark=no-mark \
</span></span><span class="line"><span class="cl">    new-connection-mark=NordVPN dst-address-list=cloudflare-ips \
</span></span><span class="line"><span class="cl">    in-interface-list=LAN comment=&#34;Cloudflare through VPN during LaLiga blocks&#34; \
</span></span><span class="line"><span class="cl">    disabled=yes
</span></span></code></pre></div><p>Finally, this script checks Hayahora every five minutes. More than ten DNS answers is my signal that a blocking event is active. The same threshold is used by the <a href="https://github.com/taichikuji/trmnl-laliga-plugin">TRMNL LaLiga plugin</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="cl"><span class="o">/</span><span class="n">system</span> <span class="n">script</span>
</span></span><span class="line"><span class="cl"><span class="n">add</span> <span class="n">name</span><span class="o">=</span><span class="n">laliga</span><span class="o">-</span><span class="n">cloudflare</span><span class="o">-</span><span class="k">switch</span> <span class="n">policy</span><span class="o">=</span><span class="n">ftp</span><span class="p">,</span><span class="n">read</span><span class="p">,</span><span class="n">write</span><span class="p">,</span><span class="n">policy</span><span class="p">,</span><span class="n">test</span> <span class="n">source</span><span class="o">=</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">:</span><span class="n">local</span> <span class="n">rule</span> <span class="p">[</span><span class="o">/</span><span class="n">ip</span> <span class="n">firewall</span> <span class="n">mangle</span> <span class="n">find</span> <span class="n">where</span> <span class="n">comment</span><span class="o">=</span><span class="s2">&#34;Cloudflare through VPN during LaLiga blocks&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">:</span><span class="n">local</span> <span class="n">result</span> <span class="p">[</span><span class="o">/</span><span class="k">tool</span> <span class="n">fetch</span> \
</span></span><span class="line"><span class="cl">        <span class="n">url</span><span class="o">=</span><span class="s2">&#34;https://dns.google/resolve?name=blocked.dns.hayahora.futbol&amp;type=A&#34;</span> \
</span></span><span class="line"><span class="cl">        <span class="n">check</span><span class="o">-</span><span class="n">certificate</span><span class="o">=</span><span class="n">yes</span> <span class="n">output</span><span class="o">=</span><span class="n">user</span> <span class="n">as</span><span class="o">-</span><span class="n">value</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">:</span><span class="n">local</span> <span class="n">dns</span> <span class="p">[:</span><span class="n">deserialize</span> <span class="p">(</span><span class="o">$</span><span class="n">result</span><span class="o">-&gt;</span><span class="s2">&#34;data&#34;</span><span class="p">)</span> <span class="n">from</span><span class="o">=</span><span class="n">json</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">:</span><span class="n">local</span> <span class="n">answers</span> <span class="p">(</span><span class="o">$</span><span class="n">dns</span><span class="o">-&gt;</span><span class="s2">&#34;Answer&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">:</span><span class="n">local</span> <span class="n">blocked</span> <span class="p">(((</span><span class="o">$</span><span class="n">dns</span><span class="o">-&gt;</span><span class="s2">&#34;Status&#34;</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="p">([:</span><span class="n">len</span> <span class="o">$</span><span class="n">answers</span><span class="p">]</span> <span class="o">&gt;</span> <span class="mi">10</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="o">/</span><span class="n">ip</span> <span class="n">firewall</span> <span class="n">mangle</span> <span class="n">set</span> <span class="o">$</span><span class="n">rule</span> <span class="n">disabled</span><span class="o">=</span><span class="p">(</span><span class="o">!$</span><span class="n">blocked</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">/</span><span class="n">system</span> <span class="n">scheduler</span>
</span></span><span class="line"><span class="cl"><span class="n">add</span> <span class="n">name</span><span class="o">=</span><span class="n">laliga</span><span class="o">-</span><span class="n">cloudflare</span><span class="o">-</span><span class="k">switch</span> <span class="n">interval</span><span class="o">=</span><span class="mi">5</span><span class="n">m</span> <span class="n">on</span><span class="o">-</span><span class="n">event</span><span class="o">=</span><span class="n">laliga</span><span class="o">-</span><span class="n">cloudflare</span><span class="o">-</span><span class="k">switch</span>
</span></span></code></pre></div><p>You might wonder why this does an HTTP request to Google DNS instead of using RouterOS&rsquo;s native <code>:resolve</code>. <code>:resolve</code> returns only one record. I need the full answer set to keep the <code>&gt; 10</code> threshold, so one record does not send all Cloudflare traffic through the VPN by accident.</p>
<h2 id="a-couple-of-limits">A couple of limits</h2>
<p>This is a blunt workaround. While the rule is enabled, every new connection from my LAN to a Cloudflare IP goes through the VPN. That is fine for me during a match, but it sends all Cloudflare traffic through the VPN, not just traffic to the site that failed.</p>
<p>The DNS data is an observation, not an official blocklist. And connections that already exist keep their old route until they reconnect. Still, it has saved me from one more round of &ldquo;why is this website broken only tonight?&rdquo; If football is on and the block is active, I am French automatically.</p>]]></content:encoded>
    </item>
    
  </channel>
</rss>
