From cb7b1804cf896627546c280d92ea0690274f7c58 Mon Sep 17 00:00:00 2001 From: Timothy Miller <46549361+timothymiller@users.noreply.github.com> Date: Wed, 15 Feb 2023 16:14:22 -0500 Subject: [PATCH] [feature] Extract IP address from netif credit: @comicchang --- README.md | 24 +++++++++-- cloudflare-ddns.py | 99 ++++++++++++++++++++++++++++++--------------- config-example.json | 4 +- requirements.txt | 3 +- 4 files changed, 92 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 920ea65..a37a93f 100755 --- a/README.md +++ b/README.md @@ -10,10 +10,12 @@ Access your home network remotely via a custom domain name without a static IP! - 🔁 The Python runtime will re-use existing HTTP connections. - 🗃️ Cloudflare API responses are cached to reduce API usage. - 🤏 The Docker image is small and efficient. -- 0️⃣ Zero dependencies. +- 0️⃣ Zero weirdo dependencies. - 💪 Supports all platforms. - 🏠 Enables low cost self hosting to promote a more decentralized internet. -- 🔒 Zero-log IP provider ([cdn-cgi/trace](https://www.cloudflare.com/cdn-cgi/trace)) +- 🧑‍🚀 Supports NAT and multiple network interfaces. + - 🔒 HTTP [(Zero-log IP provider)](https://www.cloudflare.com/cdn-cgi/trace) + - 💻 [Netif](https://pypi.org/project/netifaces/): `ppp0`, `eth0`, `wlan0`, etc. - 👐 GPL-3.0 License. Open source for open audits. ## 💯 Complete Support of Domain Names, Subdomains, and IPv4 & IPv6 @@ -69,6 +71,15 @@ Some ISP provided modems only allow port forwarding over IPv4 or IPv6. In this c "aaaa": true ``` +### 🧑‍🚀 Method used for getting IP address + +If you have multiple network interfaces, or if you are behind nat, you may need to enable alternative way to determine your IP address(es). + +```json +"method": "netif", +"interface": "ppp0" +``` + ### 🎛️ Other values explained ```json @@ -123,7 +134,9 @@ Do not include the base domain name in your `subdomains` config. Do not use the "a": true, "aaaa": true, "purgeUnknownRecords": false, - "ttl": 300 + "ttl": 300, + "method": "http", + "interface": "" } ``` @@ -181,7 +194,10 @@ If you are using API Tokens, make sure the token used supports editing your zone ], "a": true, "aaaa": true, - "purgeUnknownRecords": false + "purgeUnknownRecords": false, + "ttl": 300, + "method": "http", + "interface": "" } ``` diff --git a/cloudflare-ddns.py b/cloudflare-ddns.py index 0f01142..6a0a955 100755 --- a/cloudflare-ddns.py +++ b/cloudflare-ddns.py @@ -6,7 +6,7 @@ # A small, 🕵️ privacy centric, and ⚡ # lightning fast multi-architecture Docker image for self hosting projects. -__version__ = "1.0.2" +__version__ = "1.0.3" import json import os @@ -15,6 +15,7 @@ import sys import threading import time import requests +import netifaces as ni CONFIG_PATH = os.environ.get('CONFIG_PATH', os.getcwd()) @@ -56,53 +57,80 @@ def getIPs(): global ipv4_enabled global ipv6_enabled global purgeUnknownRecords + global method + global interfaces + if ipv4_enabled: - try: - a = requests.get( - "https://1.1.1.1/cdn-cgi/trace").text.split("\n") - a.pop() - a = dict(s.split("=") for s in a)["ip"] - except Exception: - global shown_ipv4_warning - if not shown_ipv4_warning: - shown_ipv4_warning = True - print("🧩 IPv4 not detected via 1.1.1.1, trying 1.0.0.1") - # Try secondary IP check + if method == 'http': try: a = requests.get( - "https://1.0.0.1/cdn-cgi/trace").text.split("\n") + "https://1.1.1.1/cdn-cgi/trace").text.split("\n") a.pop() a = dict(s.split("=") for s in a)["ip"] except Exception: - global shown_ipv4_warning_secondary - if not shown_ipv4_warning_secondary: - shown_ipv4_warning_secondary = True - print("🧩 IPv4 not detected via 1.0.0.1. Verify your ISP or DNS provider isn't blocking Cloudflare's IPs.") + global shown_ipv4_warning + if not shown_ipv4_warning: + shown_ipv4_warning = True + print("🧩 IPv4 not detected via 1.1.1.1, trying 1.0.0.1") + # Try secondary IP check + try: + a = requests.get( + "https://1.0.0.1/cdn-cgi/trace").text.split("\n") + a.pop() + a = dict(s.split("=") for s in a)["ip"] + except Exception: + global shown_ipv4_warning_secondary + if not shown_ipv4_warning_secondary: + shown_ipv4_warning_secondary = True + print("🧩 IPv4 not detected via 1.0.0.1. Verify your ISP or DNS provider isn't blocking Cloudflare's IPs.") + if purgeUnknownRecords: + deleteEntries("A") + else: + try: + a = ni.ifaddresses(interface)[ni.AF_INET][0]['addr'] + except Exception: + global shown_ipv4_warning + if not shown_ipv4_warning: + shown_ipv4_warning = True + print("🧩 IPv4 not detected via " + interface + ". Verify your interface is up and has an IPv4 address.") if purgeUnknownRecords: deleteEntries("A") + if ipv6_enabled: - try: - aaaa = requests.get( - "https://[2606:4700:4700::1111]/cdn-cgi/trace").text.split("\n") - aaaa.pop() - aaaa = dict(s.split("=") for s in aaaa)["ip"] - except Exception: - global shown_ipv6_warning - if not shown_ipv6_warning: - shown_ipv6_warning = True - print("🧩 IPv6 not detected via 1.1.1.1, trying 1.0.0.1") + if method == 'http': try: aaaa = requests.get( - "https://[2606:4700:4700::1001]/cdn-cgi/trace").text.split("\n") + "https://[2606:4700:4700::1111]/cdn-cgi/trace").text.split("\n") aaaa.pop() aaaa = dict(s.split("=") for s in aaaa)["ip"] except Exception: - global shown_ipv6_warning_secondary - if not shown_ipv6_warning_secondary: - shown_ipv6_warning_secondary = True - print("🧩 IPv6 not detected via 1.0.0.1. Verify your ISP or DNS provider isn't blocking Cloudflare's IPs.") + global shown_ipv6_warning + if not shown_ipv6_warning: + shown_ipv6_warning = True + print("🧩 IPv6 not detected via 1.1.1.1, trying 1.0.0.1") + try: + aaaa = requests.get( + "https://[2606:4700:4700::1001]/cdn-cgi/trace").text.split("\n") + aaaa.pop() + aaaa = dict(s.split("=") for s in aaaa)["ip"] + except Exception: + global shown_ipv6_warning_secondary + if not shown_ipv6_warning_secondary: + shown_ipv6_warning_secondary = True + print("🧩 IPv6 not detected via 1.0.0.1. Verify your ISP or DNS provider isn't blocking Cloudflare's IPs.") + if purgeUnknownRecords: + deleteEntries("AAAA") + else: + try: + aaaa = ni.ifaddresses(interface)[ni.AF_INET6][0]['addr'] + except Exception: + global shown_ipv6_warning + if not shown_ipv6_warning: + shown_ipv6_warning = True + print("🧩 IPv6 not detected via " + interface + ". Verify your interface is up and has an IPv6 address.") if purgeUnknownRecords: deleteEntries("AAAA") + ips = {} if (a is not None): ips["ipv4"] = { @@ -232,6 +260,8 @@ if __name__ == '__main__': ipv4_enabled = True ipv6_enabled = True purgeUnknownRecords = False + method = 'http' + interface = '' if sys.version_info < (3, 5): raise Exception("🐍 This script requires Python 3.5+") @@ -253,6 +283,11 @@ if __name__ == '__main__': ipv4_enabled = True ipv6_enabled = True print("⚙️ Individually disable IPv4 or IPv6 with new config.json options. Read more about it here: https://github.com/timothymiller/cloudflare-ddns/blob/master/README.md") + try: + method = config["method"] + interface = config["interface"] + except: + print("⚙️ No config detected for 'method' - defaulting to 'http'") try: purgeUnknownRecords = config["purgeUnknownRecords"] except: diff --git a/config-example.json b/config-example.json index 38f1097..945d8e4 100755 --- a/config-example.json +++ b/config-example.json @@ -24,5 +24,7 @@ "a": true, "aaaa": true, "purgeUnknownRecords": false, - "ttl": 300 + "ttl": 300, + "method": "http", + "interface": "" } diff --git a/requirements.txt b/requirements.txt index bf0d9d4..cc0f731 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ -requests==2.28.2 \ No newline at end of file +requests==2.28.2 +netifaces==0.11.0 \ No newline at end of file