• Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Search
  • Register
  • Login
Netgate Discussion Forum
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Search
  • Register
  • Login

Cloudflare DNS with multiple WAN

Scheduled Pinned Locked Moved Routing and Multi WAN
4 Posts 2 Posters 293 Views
Loading More Posts
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • C
    Cabu
    last edited by Cabu May 4, 2025, 12:03 PM May 4, 2025, 11:56 AM

    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 :(
    I should probably note that the routers for SL_WAN and P_WAN are not in bridge mode.

    C 1 Reply Last reply May 4, 2025, 3:26 PM Reply Quote 0
    • C
      Cabu @Cabu
      last edited by May 4, 2025, 3:26 PM

      I worked around by using a custom dynamic dns client that run this script as a cgi from my webserver:

      #!/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}")
      

      I also ensure that all requests to checkip.dyndns.org go through my P_WAN with a rule :)

      S C 2 Replies Last reply May 4, 2025, 4:50 PM Reply Quote 0
      • S
        SteveITS Galactic Empire @Cabu
        last edited by May 4, 2025, 4:50 PM

        @Cabu Sounds like the bug listed at https://docs.netgate.com/pfsense/en/latest/releases/25-03.html#dynamic-dns ?

        Pre-2.7.2/23.09: Only install packages for your version, or risk breaking it. Select your branch in System/Update/Update Settings.
        When upgrading, allow 10-15 minutes to restart, or more depending on packages and device speed.
        Upvote πŸ‘ helpful posts!

        1 Reply Last reply Reply Quote 1
        • C
          Cabu @Cabu
          last edited by May 6, 2025, 12:20 AM

          Oups the script was not complete :( Here an updated version :)

          #!/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()
          
          1 Reply Last reply Reply Quote 0
          4 out of 4
          • First post
            4/4
            Last post
          Copyright 2025 Rubicon Communications LLC (Netgate). All rights reserved.
            This community forum collects and processes your personal information.
            consent.not_received