<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Cloudflare DNS with multiple WAN]]></title><description><![CDATA[<p dir="auto">I have setup a multi wan config with starlink (SL_WAN) without public IP for high throughput and my ADSL provider (P_WAN) for its ability to have a public dynamic IP. But the Cloudflare Dynamic DNS client who is setup to use the P_WAN interface, send my SL_WAN IP to update the records :(<br />
I should probably note that the routers for SL_WAN and P_WAN are not in bridge mode.</p>
]]></description><link>https://forum.netgate.com/topic/197354/cloudflare-dns-with-multiple-wan</link><generator>RSS for Node</generator><lastBuildDate>Sun, 14 Jun 2026 10:14:16 GMT</lastBuildDate><atom:link href="https://forum.netgate.com/topic/197354.rss" rel="self" type="application/rss+xml"/><pubDate>Sun, 04 May 2025 11:56:26 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to Cloudflare DNS with multiple WAN on Tue, 06 May 2025 00:20:29 GMT]]></title><description><![CDATA[<p dir="auto">Oups the script was not complete :( Here an updated version :)</p>
<pre><code class="language-Python">#!/var/www/cloudflare/venv/bin/python3
# -*- coding: utf-8 -*-

import requests
import json
import re
import os

# Cloudflare API settings
API_TOKEN = 'my_api_token'
ZONE_ID = 'my_zone_id'

# TTL constant (120 = "Auto" on Cloudflare)
AUTO = 120

# DNS records to update
RECORDS_TO_UPDATE = [
    {'name': 'domain.org', 'type': 'A', 'proxied': True, 'ttl': AUTO},
    {'name': '*.domain.org', 'type': 'A', 'proxied': True, 'ttl': AUTO},
    {'name': 'minecraft.domain.org', 'type': 'A', 'proxied': False, 'ttl': AUTO}
]

# API headers
HEADERS = {
    'Authorization': f'Bearer {API_TOKEN}',
    'Content-Type': 'application/json',
}

# File path to store last known IP
LAST_IP_FILE = os.path.join(os.path.dirname(__file__), 'last_ip.txt')


def get_public_ip():
    """Fetch current public IP from checkip.dyndns.org"""
    try:
        response = requests.get("http://checkip.dyndns.org/")
        ip = re.search(r"Current IP Address: ([\d.]+)", response.text).group(1)
        return ip
    except Exception as e:
        raise RuntimeError(f"Failed to detect public IP: {e}")


def load_last_ip():
    """Read the last saved public IP address"""
    try:
        with open(LAST_IP_FILE, 'r') as f:
            return f.read().strip()
    except FileNotFoundError:
        return None


def save_current_ip(ip):
    """Save the current public IP address"""
    with open(LAST_IP_FILE, 'w') as f:
        f.write(ip)


def get_all_dns_records():
    """Fetch all DNS records in the Cloudflare zone"""
    url = f'https://api.cloudflare.com/client/v4/zones/{ZONE_ID}/dns_records'
    response = requests.get(url, headers=HEADERS)

    if response.status_code != 200:
        raise RuntimeError(f"Failed to fetch DNS records: {response.text}")

    return response.json().get('result', [])


def update_dns_record(record_id, name, record_type, proxied, ttl, new_ip):
    """Update a DNS record on Cloudflare"""
    url = f'https://api.cloudflare.com/client/v4/zones/{ZONE_ID}/dns_records/{record_id}'
    payload = { 'type': record_type, 'name': name, 'content': new_ip, 'ttl': ttl, 'proxied': proxied }
    response = requests.put(url, headers=HEADERS, data=json.dumps(payload))

    if response.status_code == 200:
        print(f"✅ Updated {name} to {new_ip}")
    else:
        raise RuntimeError(f"Failed to update {name}: {response.text}")


def main():
    # Output HTTP header (for CGI)
    print('Content-Type: text/plain; charset=utf-8\n')

    try:
        current_ip = get_public_ip()
        last_ip = load_last_ip()

        print(f"🌍 Current Public IP: {current_ip}")
        if current_ip == last_ip:
            print("⏸️  Public IP has not changed. Skipping update.")
            return

        all_records = get_all_dns_records()
        record_id_map = {(rec['name'], rec['type']): rec['id'] for rec in all_records}

        all_success = True

        for record in RECORDS_TO_UPDATE:
            key = (record['name'], record['type'])
            record_id = record_id_map.get(key)

            if not record_id:
                print(f"⚠️ No record ID found for {record['name']} ({record['type']})")
                all_success = False
                continue

            try:
                update_dns_record(record_id, record['name'], record['type'], record['proxied'], record['ttl'], current_ip)
            except Exception as e:
                print(f"❌ Failed to update {record['name']}: {e}")
                all_success = False

        if all_success:
            save_current_ip(current_ip)
            print("✅ All records updated successfully. IP saved.")
        else:
            print("⚠️ Some updates failed. IP not saved to ensure retry next time.")

    except Exception as e:
        print(f"🚫 Script failed: {e}")


if __name__ == '__main__':
    main()
</code></pre>
]]></description><link>https://forum.netgate.com/post/1214017</link><guid isPermaLink="true">https://forum.netgate.com/post/1214017</guid><dc:creator><![CDATA[Cabu]]></dc:creator><pubDate>Tue, 06 May 2025 00:20:29 GMT</pubDate></item><item><title><![CDATA[Reply to Cloudflare DNS with multiple WAN on Sun, 04 May 2025 16:50:59 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/cabu">@<bdi>Cabu</bdi></a> Sounds like the bug listed at https://docs.netgate.com/pfsense/en/latest/releases/25-03.html#dynamic-dns ?</p>
]]></description><link>https://forum.netgate.com/post/1213936</link><guid isPermaLink="true">https://forum.netgate.com/post/1213936</guid><dc:creator><![CDATA[SteveITS]]></dc:creator><pubDate>Sun, 04 May 2025 16:50:59 GMT</pubDate></item><item><title><![CDATA[Reply to Cloudflare DNS with multiple WAN on Sun, 04 May 2025 15:26:36 GMT]]></title><description><![CDATA[<p dir="auto">I worked around by using a custom dynamic dns client that run this script as a cgi from my webserver:</p>
<pre><code class="language-Python">#!/var/www/cloudflare/venv/bin/python3
# -*- coding: utf-8 -*-

import requests
import json
import re

# Cloudflare API settings
API_TOKEN = 'my_api_token'
ZONE_ID = 'my_zone_id'

# TTL constant
AUTO = 120  # 120 = Auto

# DNS records to update
RECORDS_TO_UPDATE = [
    {'name': 'domain.org', 'type': 'A', 'proxied': True, 'ttl': AUTO},
    {'name': '*.domain.org', 'type': 'A', 'proxied': True, 'ttl': AUTO},
    {'name': 'minecraft.domain.org', 'type': 'A', 'proxied': False, 'ttl': AUTO}
]

# API headers
HEADERS = {
    'Authorization': f'Bearer {API_TOKEN}',
    'Content-Type': 'application/json',
}


def get_public_ip():
    """Fetch current public IP from checkip.dyndns.org"""
    try:
        response = requests.get("http://checkip.dyndns.org/")
        ip = re.search(r"Current IP Address: ([\d.]+)", response.text).group(1)
        return ip
    except Exception as e:
        raise RuntimeError(f"Failed to detect public IP: {e}")


def get_dns_record_id(record_name, record_type):
    """Retrieve DNS record ID from Cloudflare"""
    url = f'https://api.cloudflare.com/client/v4/zones/{ZONE_ID}/dns_records'
    params = {'type': record_type, 'name': record_name}
    response = requests.get(url, headers=HEADERS, params=params)

    if response.status_code != 200:
        raise RuntimeError(f"Failed to fetch DNS record: {response.text}")

    result = response.json().get('result', [])
    if not result:
        raise RuntimeError(f"No matching DNS record for {record_name} ({record_type})")

    return result[0]['id']


def update_dns_record(record_id, name, record_type, proxied, ttl, new_ip):
    """Update a specific DNS record"""
    url = f'https://api.cloudflare.com/client/v4/zones/{ZONE_ID}/dns_records/{record_id}'
    payload = {
        'type': record_type,
        'name': name,
        'content': new_ip,
        'ttl': ttl,
        'proxied': proxied
    }
    response = requests.put(url, headers=HEADERS, data=json.dumps(payload))

    if response.status_code == 200:
        print(f"✅ Updated {name} to {new_ip}")
    else:
        print(f"❌ Failed to update {name}: {response.text}")
</code></pre>
<p dir="auto">I also ensure that all requests to checkip.dyndns.org go through my P_WAN with a rule :)</p>
]]></description><link>https://forum.netgate.com/post/1213934</link><guid isPermaLink="true">https://forum.netgate.com/post/1213934</guid><dc:creator><![CDATA[Cabu]]></dc:creator><pubDate>Sun, 04 May 2025 15:26:36 GMT</pubDate></item></channel></rss>