折腾一番CloudFlare DDNS

封面

TL;DR

今年一开年,Surprise!ipv4家宽开服全寄!
趁着过年走亲戚的时候拿Natter测了一下,我家、亲戚家的网络都是NAT 4类型的。好,好,太好了。

开学回校后,恰逢校园网基础设施更新,听门口大爷说是上个学期换的一批网络设备坏了返厂维修,开学这段时间到货学校里。
我说呢回校第一天怎么宿舍里没WiFi但是图书馆WiFi一切正常,过两天就有WiFi了而且还是WiFi6!天翼3G太快辣!!!
继续拿Natter测了一下,还是NAT 4,🆗。
出寝室买饭还能看到几个技术员在一楼开柜子弄交换机,不过这几天似乎没动静了,但有线网口还是获取不到ip地址,所以有线宽带什么时候恢复

前期探索

在上个学期,同学送我了个中国电信4K超高清机顶盒,芯片是晶晨的Amlogic S905X,内存1GB外存硬盘6Gb,刷了个ArmBian系统。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    _    ____  __  __        __   _  _
/ \ | _ \| \/ | / /_ | || |
/ _ \ | |_) | |\/| |_____| '_ \| || |_
/ ___ \| _ <| | | |_____| (_) |__ _|
/_/ \_\_| \_\_| |_| \___/ |_|

Welcome to Ubuntu 20.04.5 LTS with Linux 5.9.0-arm-64

No end-user support: built from trunk

System load: 3% Up time: 2 min
Memory usage: 14% of 924M IP: 192.168.1.102
CPU temp: 43°C Usage of /: 30% of 5.8G

Last login: Sun Feb 25 13:17:07 2024
root@arm-64:~# neofetch
root@arm-64
-----------
█ █ █ █ █ █ █ █ █ █ █ OS: Armbian focal (20.10) aarch64
███████████████████████ Host: Amlogic Meson GXL (S905X) P212 Development Board
▄▄██ ██▄▄ Kernel: 5.9.0-arm-64
▄▄██ ███████████ ██▄▄ Uptime: 2 mins
▄▄██ ██ ██ ██▄▄ Packages: 515 (dpkg)
▄▄██ ██ ██ ██▄▄ Shell: bash 5.0.17
▄▄██ ██ ██ ██▄▄ Resolution: 720x576i
▄▄██ █████████████ ██▄▄ Terminal: /dev/pts/0
▄▄██ ██ ██ ██▄▄ CPU: (4) @ 2.016GHz
▄▄██ ██ ██ ██▄▄ Memory: 132MiB / 924MiB
▄▄██ ██ ██ ██▄▄
▄▄██ ██▄▄
███████████████████████
█ █ █ █ █ █ █ █ █ █ █

上个学期我拿它配置了一个FRP连到我国内公网VPS,把寝室环境暴露出去然后方便远程办公。大概细节是,这台中国电信4K超高清机顶盒充当跳板机,寝室路由器只开放一个端口供我在图书馆通过校园大内网连回寝室跳板机,然后访问位于宿舍的电脑。但是这只解决了校内的互联,还要处理校外公网环境以及回家后怎么办。于是我在跳板机里部署了一个代理软件的服务端,并在后台使用systemd对其进行保活,再用ssh的端口映射将代理软件监听的tcp端口映射到国内公网的VPS上。人在校外,通过手机上或者苏菲Surface上的代理软件连到国内公网VPS的映射端口,这个时候流量会回到跳板机,再访问内网资源。

跳板机
跳板机
笔记本电脑
笔记本电脑
别的电子设备
别的电子设备
宿舍服务器
宿舍服务器
苏菲
苏菲
宿舍路由器
宿舍路由器
校内访问
校内访问
校园网
校园网
数据访问通路
数据访问通路
Text is not SVG - cannot display

跳板机
跳板机
笔记本电脑
笔记本电脑
别的电子设备
别的电子设备
宿舍服务器
宿舍服务器
国内VPS
国内VPS
手机热点
手机热点
苏菲
苏菲
流量卡基站
流量卡基站
数据访问通路
数据访问通路
宿舍路由器
宿舍路由器
校园网路由器
校园网路由器
校外访问
校外访问
Text is not SVG - cannot display

(图片使用Draw IO绘制svg)

这么做确实挺方便,我在去年考研期间就是全程这样操作的,因为实在不想把游戏本带到图书馆,不仅电源大还重,还不支持充电宝供电,而且这电脑在买来一年不到的时候因内存条虚焊而跟售后扯皮一个月才同意给我保内换主板。

这么做的问题在于,需要一个有线的网络环境,最差情况就是路由器要支持无线桥接。 但是我宿舍里的路由器是2010年代的上古TP-Link,而且截至今天写稿,已回校一周,宿舍有线宽带依旧没恢复,怎么办呢?重买解决100%问题

我开始考虑CloudFlareDDNS服务了。恰好大一办的校园流量卡一个月能有150GB校内流量,反正用不完何不用来给电脑搞个ipv6的ddns呢?甚至还能有全球的公网ip,一举两得!

CloudFlare DDNS的配置

上网找了一圈,全是用第三方集成式的开源项目或者OpenWRT里的DDNS模块。
我的需求是:

能在WindowsLinux平台下通跑。
无视CPU平台限制。
强调在低性能的Linux平台下的高运行效率。
使用配置文件进行DDNS,不能把用户信息写死进脚本里。

最终决定使用ShellPython各编写一份,Linux平台下用ShellWindows下用Python
在编写时参考了wherelse/cloudflare-ddns-script的脚本。

第一次运行时,会在脚本同级目录下生成config.txt,向里面填写你的个人信息,然后重新运行即可。**Python版和Shell版的配置文件通用!**

配置文件填写参考这里:

1
2
3
4
5
6
7
8
9
10
11
12
13
cf_email_addr=a@b.c  # cloudFlare注册账户邮箱
cf_global_api_key=1145141919810 # 你的cloudflare账户Globel API Key
domain_main=example.com # 你的主域名, 如example.com
domain_full_ddns=ddnsv6.example.com # ddns用的完整域名, 如ddns.example.com
record_type=AAAA # A or AAAA,ipv4 或 ipv6解析
eth_card=eth0 # 使用本地方式获取ip绑定的网卡, 如eth0
ip_index=local # use "internet" or "local",使用本地方式还是网络方式获取地址
reset_ip=0 # 1为将ddns的ip改为localhost,2为手动输入ip,其他任意值为不是
enable_cache=0 # 1为保存缓存文件,其他任意值为不保存。保存缓存文件可让下次ddns更新速度更快,但缓存文件泄漏需要你去把cf_global_api_key重新revoke一次,不然会造成隐私泄漏
skip_connectivity_check=0 # 0为不跳过脚本最开始运行的时候的检查连接
ip_file=ip.txt # 保存地址信息
id_file=cloudflare.ids
log_file=cloudflare.log

这是最终的Shell脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
#!/bin/bash

# CHANGE THESE
cf_email_addr="" # cloudFlare注册账户邮箱
cf_global_api_key="" # 你的cloudflare账户Globel API Key
domain_main="" # 你的主域名, 如google.com
domain_full_ddns="" # ddns用的完整域名, 如ddns.google.com
record_type="" # A or AAAA,ipv4 或 ipv6解析

eth_card="" # 使用本地方式获取ip绑定的网卡, 如eth0
ip_index="local" # use "internet" or "local",使用本地方式还是网络方式获取地址

reset_ip=0 # 1为将ddns的ip改为localhost,2为手动输入ip,其他任意值为不是
enable_cache="0" # 1为保存缓存文件,其他任意值为不保存。保存缓存文件可让下次ddns更新速度更快,但缓存文件泄漏需要你去把cf_global_api_key重新revoke一次,不然会造成隐私泄漏
skip_connectivity_check="0" # 0为不跳过脚本最开始运行的时候的检查连接
ip_file="ip.txt" # 保存地址信息
id_file="cloudflare.ids"
log_file="cloudflare.log"

# 默认配置。需要和上面的变量顺序相同
default_config_arr=("","","","","","","local","0","0","0","ip.txt","cloudflare.ids","cloudflare.log")
config_file="config.txt"

create_default_config_file() {
echo "cf_email_addr=" >> $config_file
echo "cf_global_api_key=" >> $config_file
echo "domain_main=" >> $config_file
echo "domain_full_ddns=" >> $config_file
echo "record_type=" >> $config_file
echo "eth_card=" >> $config_file
echo "ip_index=local" >> $config_file
echo "reset_ip=0" >> $config_file
echo "enable_cache=0" >> $config_file
echo "skip_connectivity_check" >> $config_file
echo "ip_file=ip.txt" >> $config_file
echo "id_file=cloudflare.ids" >> $config_file
echo "log_file=cloudflare.log" >> $config_file
}



# 声明日志函数
log() {
if [ "$1" ]; then
if [ "$enable_cache" -eq "1" ]; then
echo -e "[$(date)] - $1" >> $log_file
fi
echo "[$(date)] - $1"
fi
}
log "Init config"


# 初始化配置文件

if [ -e "$config_file" ]; then
log "File $config_file exists."
else
log "File $config_file does not exist."
create_default_config_file
log "请将该配置文件填写完成:${config_file}"
exit 1
fi

read_config_key() {
key=$1
value=$(grep "^${key}=" $config_file | cut -d'=' -f2)
echo $value
}

cf_email_addr=$(read_config_key "cf_email_addr")
cf_global_api_key=$(read_config_key "cf_global_api_key")
domain_main=$(read_config_key "domain_main")
domain_full_ddns=$(read_config_key "domain_full_ddns")
record_type=$(read_config_key "record_type")
eth_card=$(read_config_key "eth_card")
ip_index=$(read_config_key "ip_index")
reset_ip=$(read_config_key "reset_ip")
enable_cache=$(read_config_key "enable_cache")
skip_connectivity_check=$(read_config_key "skip_connectivity_check")
ip_file=$(read_config_key "ip_file")
id_file=$(read_config_key "id_file")
log_file=$(read_config_key "log_file")


# 检查配置文件是否存在以及是否有进行填写
got_config=()
got_config+=${cf_email_addr}
got_config+=${cf_global_api_key}
got_config+=${domain_main}
got_config+=${domain_full_ddns}
got_config+=${record_type}
got_config+=${eth_card}
got_config+=${ip_index}
got_config+=${reset_ip}
got_config+=${enable_cache}
got_config+=${skip_connectivity_check}
got_config+=${ip_file}
got_config+=${id_file}
got_config+=${log_file}


# 将数组默认配置连接成字符串
def_str=""
for element in "${default_config_arr[@]}"
do
def_str="$def_str$element"
done

# 将获取到的配置连接成字符串
got_str=""
for element in "${got_config[@]}"
do
got_str="$got_str$element"
done

# 判断字符串是否相等,即用户是否填写了配置文件
if [ "$def_str" = "$got_str" ]; then
create_default_config_file
log "请将该配置文件填写完成:${config_file}"
fi

# 检查连接
if [ "$skip_connectivity_check" -eq "0" ]; then
log "Checking connectivity to Cloudflare"
# 检查和api.cloudflare.com的连接性
if curl --connect-timeout 30 -s --head https://api.cloudflare.com/ > /dev/null; then
log "Successfully connected to api.cloudflare.com"
# 连接成功,可以在这里继续后续的命令
else
log "Error connecting to api.cloudflare.com. Stopping script execution."
exit 1
fi
else
log "Skipping check connectivity to Cloudflare"
fi


# 开始根据读取到的配置进行ddns设置
log "Starting DDNS"
if [ $record_type = "AAAA" ];then
if [ "$reset_ip" -eq "1" ]; then
ip="::1"
log "Reset DDNS address to loopback..."
elif [ "$reset_ip" -eq "2" ]; then
read -p "Please enter an AAAA record IP:\n" ip
log "Got it, now setting DDNS..."
else
if [ $ip_index = "internet" ];then
ip=$(curl -6 ip.sb)
elif [ $ip_index = "local" ];then
if [ "$user" = "root" ];then
ip=$(ifconfig $eth_card | grep 'inet6'| grep -v '::1'|grep -v 'fe80' | cut -f2 | awk '{ print $2}' | head -1)
else
ip=$(/sbin/ifconfig $eth_card | grep 'inet6'| grep -v '::1'|grep -v 'fe80' | cut -f2 | awk '{ print $2}' | head -1)
fi
else
log "Error IP index, please input the right type"
exit 0
fi
fi
elif [ $record_type = "A" ];then
if [ "$enable_cache" -eq "1" ]; then
ip="127.0.0.1"
log "Reset DDNS address to loopback..."
elif [ "$reset_ip" -eq "2" ]; then
read -p "Please enter an A record IP:\n" ip
log "Got it, now setting DDNS..."
else
if [ $ip_index = "internet" ];then
ip=$(curl -4 ip.sb)
elif [ $ip_index = "local" ];then
if [ "$user" = "root" ];then
ip=$(ifconfig $eth_card | grep 'inet'| grep -v '127.0.0.1' | grep -v 'inet6'|cut -f2 | awk '{ print $2}')
else
ip=$(/sbin/ifconfig $eth_card | grep 'inet'| grep -v '127.0.0.1' | grep -v 'inet6'|cut -f2 | awk '{ print $2}')
fi
else
log "Error IP index, please input the right type"
exit 0
fi
fi
else
log "Error DNS type"
exit 0
fi

# SCRIPT START
log "Check Initiated"
log "Your ip is: ${ip}"

# 判断ip是否发生变化
if [ "$enable_cache" -eq "1" ]; then
if [ -f $ip_file ]; then
old_ip=$(cat $ip_file)
if [ $ip == $old_ip ]; then
log "IP has not changed."
exit 0
fi
fi
fi


# 获取域名和授权

if [ "$enable_cache" -eq "1" ]; then
if [ -f $id_file ] && [ $(wc -l $id_file | cut -d " " -f 1) == 2 ]; then
zone_identifier=$(head -1 $id_file)
record_identifier=$(tail -1 $id_file)
else
zone_identifier=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$domain_main" \
-H "X-Auth-Email: $cf_email_addr" \
-H "X-Auth-Key: $cf_global_api_key" \
-H "Content-Type: application/json" | grep -Po '(?<="id":")[^"]*' | head -1 )
record_identifier=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$zone_identifier/dns_records?type=${record_type}&name=$domain_full_ddns" \
-H "X-Auth-Email: $cf_email_addr" \
-H "X-Auth-Key: $cf_global_api_key" \
-H "Content-Type: application/json" | grep -Po '(?<="id":")[^"]*')
log "$zone_identifier" > $id_file
log "$record_identifier" >> $id_file
fi
else
zone_identifier=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$domain_main" \
-H "X-Auth-Email: $cf_email_addr" \
-H "X-Auth-Key: $cf_global_api_key" \
-H "Content-Type: application/json" | grep -Po '(?<="id":")[^"]*' | head -1 )
record_identifier=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$zone_identifier/dns_records?type=${record_type}&name=$domain_full_ddns" \
-H "X-Auth-Email: $cf_email_addr" \
-H "X-Auth-Key: $cf_global_api_key" \
-H "Content-Type: application/json" | grep -Po '(?<="id":")[^"]*')
fi



# 更新DNS记录
update=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$zone_identifier/dns_records/$record_identifier" \
-H "X-Auth-Email: $cf_email_addr" \
-H "X-Auth-Key: $cf_global_api_key" \
-H "Content-Type: application/json" \
--data "{\"type\":\"$record_type\",\"name\":\"$domain_full_ddns\",\"content\":\"$ip\",\"ttl\":1,\"proxied\":false}")


# 输出DDNS更新情况
if [[ $update == *"\"success\":true"* ]]; then
message="$domain_full_ddns -> $ip"
if [ "$enable_cache" -eq "1" ]; then
echo "$message" > $ip_file
fi
log message
else
message="API UPDATE FAILED. DUMPING RESULTS:\n$update"
log "$message"
exit 1
fi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
import requests
import os
import sys
import datetime
import platform
import subprocess
import re

# 需要先安装以下pip包才能运行:
# requests

# 配置变量
cf_email_addr = "" # CloudFlare注册账户邮箱
cf_global_api_key = "" # 你的Cloudflare账户Global API Key
domain_main = "" # 你的主域名, 如google.com
domain_full_ddns = "" # ddns用的完整域名, 如ddns.google.com
record_type = "AAAA" # A or AAAA, ipv4 或 ipv6解析

eth_card = "" # 使用本地方式获取IP绑定的网卡, 如eth0。此设置项只在linux上生效
ip_index = "local" # 使用 "internet" 或 "local", 获取地址的方式

reset_ip = 0 # 1为将ddns的ip改为localhost,2为手动输入ip,其他任意值为不是
enable_cache = 0 # 1为保存缓存文件,其他任意值为不保存缓存文件
skip_connectivity_check = 0 # 0为不跳过脚本最开始运行的时候的检查连接
ip_file = "ip.txt" # 保存地址信息的文件
id_file = "cloudflare.ids"
log_file = "cloudflare.log"


# 按照这个格式填入config.txt
example_config_file = {
"cf_email_addr": "", # CloudFlare注册账户邮箱
"cf_global_api_key": "", # 你的Cloudflare账户Global API Key
"domain_main": "", # 你的主域名, 如google.com
"domain_full_ddns": "", # ddns用的完整域名, 如ddns.google.com
"record_type": "", # A or AAAA, ipv4 或 ipv6解析
"eth_card": "", # 使用本地方式获取IP绑定的网卡, 如eth0。此设置项只在linux上生效
"ip_index": "local", # 使用 "internet" 或 "local", 获取地址的方式。internal不支持获取ipv4地址
"reset_ip": 0, # 1为将ddns的ip改为localhost,其他任意值为不是
"enable_cache": 0, # 1为保存缓存文件,其他任意值为不保存缓存文件
"skip_connectivity_check": 0,# 0为不跳过脚本最开始运行的时候的检查连接
"ip_file": "ip.txt", # 不需要填写
"id_file": "cloudflare.ids", # 不需要填写
"log_file": "cloudflare.log" # 不需要填写
}

def log(message):
"""日志函数"""
if message:
if enable_cache == 1:
with open(log_file, 'a') as f:
f.write(f"[{datetime.datetime.now()}] - {message}\n")
else:
print(f"[{datetime.datetime.now()}] - {message}")


def init_value():
global cf_email_addr, cf_global_api_key, domain_main, domain_full_ddns, record_type, eth_card, skip_connectivity_check, ip_index, reset_ip, enable_cache, ip_file, id_file, og_file
config_dict = {}
script_dir = os.path.dirname(os.path.abspath(__file__))
config_path = os.path.join(script_dir, "config.txt")
try:
with open(config_path, 'r', encoding='utf-8') as file:
for line in file:
line = line.strip()
if line and '=' in line:
key, value = line.split('=', 1)
config_dict[key] = value
common_keys = set(config_dict.keys()) & set(example_config_file.keys())
different_values = {}
for key in common_keys:
if config_dict[key] != example_config_file[key]:
different_values[key] = (config_dict[key], example_config_file[key])
if len(different_values) == 0:
print(f"请将该配置文件填写完成:{config_path}")
sys.exit()
log("File config.txt exists.")
cf_email_addr = config_dict.get('cf_email_addr')
cf_global_api_key = config_dict.get('cf_global_api_key')
domain_main = config_dict.get('domain_main')
domain_full_ddns = config_dict.get('domain_full_ddns')
record_type = config_dict.get('record_type')
eth_card = config_dict.get('eth_card')
ip_index = config_dict.get('ip_index')
reset_ip = int(config_dict.get('reset_ip'))
enable_cache = int(config_dict.get('enable_cache'))
skip_connectivity_check = int(config_dict.get('skip_connectivity_check'))
ip_file = config_dict.get('ip_file')
id_file = config_dict.get('id_file')
log_file = config_dict.get('log_file')
except FileNotFoundError:
with open(config_path, 'w', encoding='utf-8') as file:
for key, value in example_config_file.items():
file.write(f'{key}={value}\n')
#json.dump(example_config_file, file, ensure_ascii=False, indent=4)
print(f"请将该配置文件填写完成:{config_path}")
sys.exit()

def get_ipv6_address_windows():
"""在Windows上获取临时IPv6地址"""
try:
# 执行ipconfig命令并捕获输出
output = subprocess.check_output(["ipconfig"], text=True)
# 使用正则表达式查找临时IPv6地址
match = re.search(r"临时 IPv6 地址.*: ([a-fA-F0-9:]+)", output)
if match:
return match.group(1)
else:
return None
except Exception as e:
print(f"Error: {e}")
return None

def get_ipv6_address_linux():
"""在Linux上获取Global IPv6地址"""
try:
# 执行ip -6 addr show命令并捕获输出
output = subprocess.check_output(["ip", "-6", "addr", "show", eth_card], text=True)
# 使用正则表达式查找global scope的IPv6地址
match = re.search(r"inet6\s+([a-fA-F0-9:]+)/\d+\s+scope global", output)
if match:
return match.group(1)
else:
return None
except Exception as e:
print(f"Error: {e}")
return None

def get_ipv6_platform():
if platform.system() == "Windows":
return get_ipv6_address_windows()
elif platform.system() == "Linux":
return get_ipv6_address_linux()
else:
return None

def get_ip():
"""获取IP地址"""
if record_type == "AAAA":
if reset_ip == 1:
log("Reset DDNS address to loopback...")
return "::1"
elif reset_ip == 2:
ip = input("Please enter an AAAA record IP:\n")
log("Got it, now setting DDNS...")
return ip
else:
if ip_index == "internet":
return requests.get('https://api64.ipify.org?format=json').json()['ip']
elif ip_index == "local":
return get_ipv6_platform()
pass
elif record_type == "A":
if reset_ip == 1:
log("Reset DDNS address to loopback...")
return "127.0.0.1"
elif reset_ip == 2:
ip = input("Please enter an A record IP:\n")
log("Got it, now setting DDNS...")
return ip
else:
if ip_index == "internet":
return requests.get('https://api.ipify.org?format=json').json()['ip']
elif ip_index == "local":
# 这里需要自行实现本地获取IPv4地址的逻辑
pass
else:
log("Error DNS type")
exit(0)

def update_cloudflare_dns(ip):
"""更新Cloudflare DNS记录"""
headers = {
"X-Auth-Email": cf_email_addr,
"X-Auth-Key": cf_global_api_key,
"Content-Type": "application/json",
}
# 获取Zone ID
zone_response = requests.get(f"https://api.cloudflare.com/client/v4/zones?name={domain_main}", headers=headers)
zone_id = zone_response.json()['result'][0]['id']

# 获取DNS记录ID
dns_response = requests.get(f"https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records?type={record_type}&name={domain_full_ddns}", headers=headers)
dns_record_id = dns_response.json()['result'][0]['id']

# 更新DNS记录
data = {
"type": record_type,
"name": domain_full_ddns,
"content": ip,
"ttl": 1,
"proxied": False
}
update_response = requests.put(f"https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records/{dns_record_id}", headers=headers, json=data)
return update_response.json()



# 主逻辑
log("Init config")
init_value()
log("Starting DDNS")
if skip_connectivity_check == 0:
# 检查连接性
log("Checking connectivity to cloudflare")
try:
response = requests.get("https://api.cloudflare.com", timeout=30)
# 如果请求成功,状态码为200
if response.ok:
log("Successfully connected to https://api.cloudflare.com")
else:
log("Connected to https://api.cloudflare.com but received a non-success status code:", response.status_code)
except requests.exceptions.RequestException as e:
# 处理连接错误
log("Error connecting to https://api.cloudflare.com")
log(e)
sys.exit()
except requests.exceptions.Timeout:
# 处理连接超时
log("Timeout connecting to https://api.cloudflare.com")
log(e)
sys.exit()
log("Check Initiated")
else:
log("Skipping check connectivity to Cloudflare")

# 获取IP地址
ip = get_ip()
log(f"Your ip is: {ip}")

# 判断IP是否发生变化
old_ip = None
if enable_cache == 1 and os.path.exists(ip_file):
with open(ip_file) as f:
old_ip = f.read().strip()
if ip == old_ip:
if ip == None:
log("Check your Internet connection.")
else:
log("IP has not changed.")
exit(0)

# 更新DNS记录
update_result = update_cloudflare_dns(ip)
if update_result['success']:
message = f"Now {domain_full_ddns} -> {ip}"
if enable_cache == 1:
with open(ip_file, 'w') as f:
f.write(message)
log(message)
else:
message = f"API UPDATE FAILED. DUMPING RESULTS:\n{update_result}"
log(message)
exit(1)

Linux设备上的防火墙

既然有了公网ipv6地址,则不需要跳板机了,需要在终端上配置防火墙。

我用的是UFW作为我的防火墙。相关的教程直接上ArchWiki翻,写的很详细。
先启用ipv6下的ufw:将/etc/default/ufw下的IPV6值改为yes
基操,先禁个Ping
ipv6Ping:将/etc/ufw/before6.rules中的-A ufw6-before-input -p icmpv6 --icmpv6-type echo-request -j ACCEPT最后改为DROP
ipv4Ping:将/etc/ufw/before.rules中的-A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT最后改为DROP
无脑方法:把配置文件中所有有关icmpecho-request全改成DROP

放通KDE Connect:
根据官方文档所写,需要放通1714-1764的UDPTCP端口。

1
2
sudo ufw allow 1714:1764/udp
sudo ufw allow 1714:1764/tcp

放通Syncthing:
直接把软件添加到白名单即可。

1
sudo ufw allow syncthing

更新:使用域API实现

这个是配置文件,按你的实际情况填写。不想要手动创建,先运行更新记录的脚本,如果脚本发现没有这个文件会自动创建,这里只是提示你这些键值对的意义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
zone_id=你的zoneID
record_id=你要修改的记录的record_id
api_key=去创建一个只有域权限的API key,只授权修改DNS记录,将API key填这里
domain_main=你要修改的二级域名。比如你主域名是domain.com,你要给sub.domain.com添加解析记录,这里就填sub
proxied=false 是否对此记录启用cloudflare proxy cdn保护
record_type=A或者AAAA
eth_card=使用本地方式获取ip绑定的网卡, 如eth0。此项仅在linux上生效。
ip_index="internet" or "local",使用本地方式还是网络方式获取地址
reset_ip=1为将ddns的ip改为localhost,2为手动输入ip,其他任意值为不是
enable_cache=1为保存缓存文件,其他任意值为不保存。保存缓存文件可让下次ddns更新速度更快,但缓存文件泄漏需要你去把cf_global_api_key重新revoke一次,不然会造成隐私泄漏
skip_connectivity_check=0为不跳过脚本最开始运行的时候的检查连接
ip_file=ip.txt 保存地址信息的文件
id_file=cloudflare.ids
log_file=cloudflare.log

先获取你域下面所有的域名及其record_id

python版兼任linuxwindows,shell版适合性能差的设备。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#!/bin/bash

# 先用get获取到id,然后再填入配置文件
# CHANGE THESE
zone_id="" # cloudFlare里域名的zone id
record_id="" # 你要修改的二级域名的record id,通过get脚本获取
api_key="" # 你的API key,用于修改dns记录的key
domain_main="" # 你的主域名, 如google.com
proxied="false" # 是否要启用cdn。合法值只有true和false
record_type="" # A or AAAA,ipv4 或 ipv6解析

eth_card="" # 使用本地方式获取ip绑定的网卡, 如eth0
ip_index="local" # use "internet" or "local",使用本地方式还是网络方式获取地址

reset_ip=0 # 1为将ddns的ip改为localhost,2为手动输入ip,其他任意值为不是
enable_cache="0" # 1为保存缓存文件,其他任意值为不保存。保存缓存文件可让下次ddns更新速度更快,但缓存文件泄漏需要你去把cf_global_api_key重新revoke一次,不然会造成隐私泄漏
skip_connectivity_check="0" # 0为不跳过脚本最开始运行的时候的检查连接
ip_file="ip.txt" # 保存地址信息
id_file="cloudflare.ids"
log_file="cloudflare.log"

# 默认配置。需要和上面的变量顺序相同
default_config_arr=("","","","","false","","","local","0","0","0","ip.txt","cloudflare.ids","cloudflare.log")
config_file="config.txt"

create_default_config_file() {
echo "zone_id=" >> $config_file
echo "record_id=" >> $config_file
echo "api_key=" >> $config_file
echo "domain_main=" >> $config_file
echo "proxied=" >> $config_file
echo "record_type=" >> $config_file
echo "eth_card=" >> $config_file
echo "ip_index=local" >> $config_file
echo "reset_ip=0" >> $config_file
echo "enable_cache=0" >> $config_file
echo "skip_connectivity_check" >> $config_file
echo "ip_file=ip.txt" >> $config_file
echo "id_file=cloudflare.ids" >> $config_file
echo "log_file=cloudflare.log" >> $config_file
}



# 声明日志函数
log() {
if [ "$1" ]; then
if [ "$enable_cache" -eq "1" ]; then
echo -e "[$(date)] - $1" >> $log_file
fi
echo "[$(date)] - $1"
fi
}
log "Init config"


# 初始化配置文件

if [ -e "$config_file" ]; then
log "File $config_file exists."
else
log "File $config_file does not exist."
create_default_config_file
log "请将该配置文件填写完成:${config_file}"
exit 1
fi

read_config_key() {
key=$1
value=$(grep "^${key}=" $config_file | cut -d'=' -f2)
echo $value
}

zone_id=$(read_config_key "zone_id")
record_id=$(read_config_key "record_id")
api_key=$(read_config_key "api_key")
domain_main=$(read_config_key "domain_main")
proxied=$(read_config_key "proxied")
record_type=$(read_config_key "record_type")
eth_card=$(read_config_key "eth_card")
ip_index=$(read_config_key "ip_index")
reset_ip=$(read_config_key "reset_ip")
enable_cache=$(read_config_key "enable_cache")
skip_connectivity_check=$(read_config_key "skip_connectivity_check")
ip_file=$(read_config_key "ip_file")
id_file=$(read_config_key "id_file")
log_file=$(read_config_key "log_file")


# 检查配置文件是否存在以及是否有进行填写
got_config=()
got_config+=${zone_id}
got_config+=${record_id}
got_config+=${api_key}
got_config+=${domain_main}
got_config+=${proxied}
got_config+=${record_type}
got_config+=${eth_card}
got_config+=${ip_index}
got_config+=${reset_ip}
got_config+=${enable_cache}
got_config+=${skip_connectivity_check}
got_config+=${ip_file}
got_config+=${id_file}
got_config+=${log_file}


# 将数组默认配置连接成字符串
def_str=""
for element in "${default_config_arr[@]}"
do
def_str="$def_str$element"
done

# 将获取到的配置连接成字符串
got_str=""
for element in "${got_config[@]}"
do
got_str="$got_str$element"
done

# 判断字符串是否相等,即用户是否填写了配置文件
if [ "$def_str" = "$got_str" ]; then
create_default_config_file
log "请将该配置文件填写完成:${config_file}"
fi

# 检查连接
if [ "$skip_connectivity_check" -eq "0" ]; then
log "Checking connectivity to Cloudflare"
# 检查和api.cloudflare.com的连接性
if curl --connect-timeout 30 -s --head https://api.cloudflare.com/ > /dev/null; then
log "Successfully connected to api.cloudflare.com"
# 连接成功,可以在这里继续后续的命令
else
log "Error connecting to api.cloudflare.com. Stopping script execution."
exit 1
fi
else
log "Skipping check connectivity to Cloudflare"
fi


curl --request GET \
--url "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records" \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer $api_key"

这是python版的:

python版兼任linuxwindows,shell版适合性能差的设备。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import requests
import os
import sys
import datetime
import platform
import subprocess
import re

# 需要先安装以下pip包才能运行:
# requests

# 配置变量
zone_id="" # cloudFlare里域名的zone id
record_id="" # 你要修改的二级域名的record id,通过get脚本获取
api_key="" # 你的API key,用于修改dns记录的key
domain_main="" # 你的主域名, 如google.com
proxied="false" # 是否要启用cdn。合法值只有true和false
record_type = "AAAA" # A or AAAA, ipv4 或 ipv6解析

eth_card = "" # 使用本地方式获取IP绑定的网卡, 如eth0。此设置项只在linux上生效
ip_index = "local" # 使用 "internet" 或 "local", 获取地址的方式

reset_ip = 0 # 1为将ddns的ip改为localhost,2为手动输入ip,其他任意值为不是
enable_cache = 0 # 1为保存缓存文件,其他任意值为不保存缓存文件
skip_connectivity_check = 0 # 0为不跳过脚本最开始运行的时候的检查连接
ip_file = "ip.txt" # 保存地址信息的文件
id_file = "cloudflare.ids"
log_file = "cloudflare.log"


# 按照这个格式填入config.txt
example_config_file = {
"zone_id": "", # cloudFlare里域名的zone id
"record_id": "", # 你要修改的二级域名的record id,通过get脚本获取
"api_key": "", # 你的API key,用于修改dns记录的key
"domain_main": "", # 你的主域名, 如google.com
"proxied": "false", # 是否要启用cdn。合法值只有true和false
"record_type": "", # A or AAAA, ipv4 或 ipv6解析
"eth_card": "", # 使用本地方式获取IP绑定的网卡, 如eth0。此设置项只在linux上生效
"ip_index": "local", # 使用 "internet" 或 "local", 获取地址的方式。internal不支持获取ipv4地址
"reset_ip": 0, # 1为将ddns的ip改为localhost,其他任意值为不是
"enable_cache": 0, # 1为保存缓存文件,其他任意值为不保存缓存文件
"skip_connectivity_check": 0,# 0为不跳过脚本最开始运行的时候的检查连接
"ip_file": "ip.txt", # 不需要填写
"id_file": "cloudflare.ids", # 不需要填写
"log_file": "cloudflare.log" # 不需要填写
}

def log(message):
"""日志函数"""
if message:
if enable_cache == 1:
with open(log_file, 'a') as f:
f.write(f"[{datetime.datetime.now()}] - {message}\n")
else:
print(f"[{datetime.datetime.now()}] - {message}")


def init_value():
global zone_id, record_id, api_key, domain_main, proxied, record_type, eth_card, skip_connectivity_check, ip_index, reset_ip, enable_cache, ip_file, id_file, og_file
config_dict = {}
script_dir = os.path.dirname(os.path.abspath(__file__))
config_path = os.path.join(script_dir, "config.txt")
try:
with open(config_path, 'r', encoding='utf-8') as file:
for line in file:
line = line.strip()
if line and '=' in line:
key, value = line.split('=', 1)
config_dict[key] = value
common_keys = set(config_dict.keys()) & set(example_config_file.keys())
different_values = {}
for key in common_keys:
if config_dict[key] != example_config_file[key]:
different_values[key] = (config_dict[key], example_config_file[key])
if len(different_values) == 0:
print(f"请将该配置文件填写完成:{config_path}")
sys.exit()
log("File config.txt exists.")
zone_id = config_dict.get('zone_id')
record_id = config_dict.get('record_id')
api_key = config_dict.get('api_key')
domain_main = config_dict.get('domain_main')
proxied = config_dict.get('proxied')
record_type = config_dict.get('record_type')
eth_card = config_dict.get('eth_card')
ip_index = config_dict.get('ip_index')
reset_ip = int(config_dict.get('reset_ip'))
enable_cache = int(config_dict.get('enable_cache'))
skip_connectivity_check = int(config_dict.get('skip_connectivity_check'))
ip_file = config_dict.get('ip_file')
id_file = config_dict.get('id_file')
log_file = config_dict.get('log_file')
except FileNotFoundError:
with open(config_path, 'w', encoding='utf-8') as file:
for key, value in example_config_file.items():
file.write(f'{key}={value}\n')
#json.dump(example_config_file, file, ensure_ascii=False, indent=4)
print(f"请将该配置文件填写完成:{config_path}")
sys.exit()



"""更新Cloudflare DNS记录"""
def get_cloudflare_dns():
headers = {
"Authorization": "Bearer " + api_key,
"Content-Type": "application/json",
}
update_response = requests.get("https://api.cloudflare.com/client/v4/zones/" + zone_id + "/dns_records", headers=headers)
print(update_response.json())



# 主逻辑
log("Init config")
init_value()
get_cloudflare_dns()

记住你的zone_idrecord_id,填入配置文件中,然后使用下面的脚本更新解析记录:

python版兼任linuxwindows,shell版适合性能差的设备。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
#!/bin/bash


# 先用get获取到id,然后再填入配置文件
# CHANGE THESE
zone_id="" # cloudFlare里域名的zone id
record_id="" # 你要修改的二级域名的record id,通过get脚本获取
api_key="" # 你的API key,用于修改dns记录的key
domain_main="" # 你的主域名, 如google.com
proxied="false" # 是否要启用cdn。合法值只有true和false
record_type="" # A or AAAA,ipv4 或 ipv6解析

eth_card="" # 使用本地方式获取ip绑定的网卡, 如eth0
ip_index="local" # use "internet" or "local",使用本地方式还是网络方式获取地址

reset_ip=0 # 1为将ddns的ip改为localhost,2为手动输入ip,其他任意值为不是
enable_cache="0" # 1为保存缓存文件,其他任意值为不保存。保存缓存文件可让下次ddns更新速度更快,但缓存文件泄漏需要你去把cf_global_api_key重新revoke一次,不然会造成隐私泄漏
skip_connectivity_check="0" # 0为不跳过脚本最开始运行的时候的检查连接
ip_file="ip.txt" # 保存地址信息
id_file="cloudflare.ids"
log_file="cloudflare.log"

# 默认配置。需要和上面的变量顺序相同
default_config_arr=("","","","","false","","","local","0","0","0","ip.txt","cloudflare.ids","cloudflare.log")
config_file="config.txt"

create_default_config_file() {
echo "zone_id=" >> $config_file
echo "record_id=" >> $config_file
echo "api_key=" >> $config_file
echo "domain_main=" >> $config_file
echo "proxied=" >> $config_file
echo "record_type=" >> $config_file
echo "eth_card=" >> $config_file
echo "ip_index=local" >> $config_file
echo "reset_ip=0" >> $config_file
echo "enable_cache=0" >> $config_file
echo "skip_connectivity_check" >> $config_file
echo "ip_file=ip.txt" >> $config_file
echo "id_file=cloudflare.ids" >> $config_file
echo "log_file=cloudflare.log" >> $config_file
}



# 声明日志函数
log() {
if [ "$1" ]; then
if [ "$enable_cache" -eq "1" ]; then
echo -e "[$(date)] - $1" >> $log_file
fi
echo "[$(date)] - $1"
fi
}
log "Init config"


# 初始化配置文件

if [ -e "$config_file" ]; then
log "File $config_file exists."
else
log "File $config_file does not exist."
create_default_config_file
log "请将该配置文件填写完成:${config_file}"
exit 1
fi

read_config_key() {
key=$1
value=$(grep "^${key}=" $config_file | cut -d'=' -f2)
echo $value
}

zone_id=$(read_config_key "zone_id")
record_id=$(read_config_key "record_id")
api_key=$(read_config_key "api_key")
domain_main=$(read_config_key "domain_main")
proxied=$(read_config_key "proxied")
record_type=$(read_config_key "record_type")
eth_card=$(read_config_key "eth_card")
ip_index=$(read_config_key "ip_index")
reset_ip=$(read_config_key "reset_ip")
enable_cache=$(read_config_key "enable_cache")
skip_connectivity_check=$(read_config_key "skip_connectivity_check")
ip_file=$(read_config_key "ip_file")
id_file=$(read_config_key "id_file")
log_file=$(read_config_key "log_file")


# 检查配置文件是否存在以及是否有进行填写
got_config=()
got_config+=${zone_id}
got_config+=${record_id}
got_config+=${api_key}
got_config+=${domain_main}
got_config+=${proxied}
got_config+=${record_type}
got_config+=${eth_card}
got_config+=${ip_index}
got_config+=${reset_ip}
got_config+=${enable_cache}
got_config+=${skip_connectivity_check}
got_config+=${ip_file}
got_config+=${id_file}
got_config+=${log_file}


# 将数组默认配置连接成字符串
def_str=""
for element in "${default_config_arr[@]}"
do
def_str="$def_str$element"
done

# 将获取到的配置连接成字符串
got_str=""
for element in "${got_config[@]}"
do
got_str="$got_str$element"
done

# 判断字符串是否相等,即用户是否填写了配置文件
if [ "$def_str" = "$got_str" ]; then
create_default_config_file
log "请将该配置文件填写完成:${config_file}"
fi

# 检查连接
if [ "$skip_connectivity_check" -eq "0" ]; then
log "Checking connectivity to Cloudflare"
# 检查和api.cloudflare.com的连接性
if curl --connect-timeout 30 -s --head https://api.cloudflare.com/ > /dev/null; then
log "Successfully connected to api.cloudflare.com"
# 连接成功,可以在这里继续后续的命令
else
log "Error connecting to api.cloudflare.com. Stopping script execution."
exit 1
fi
else
log "Skipping check connectivity to Cloudflare"
fi


# 开始根据读取到的配置进行ddns设置
log "Starting DDNS"
if [ $record_type = "AAAA" ];then
if [ "$reset_ip" -eq "1" ]; then
ip="::1"
log "Reset DDNS address to loopback..."
elif [ "$reset_ip" -eq "2" ]; then
read -p "Please enter an AAAA record IP:\n" ip
log "Got it, now setting DDNS..."
else
if [ $ip_index = "internet" ];then
ip=$(curl -6 ip.sb)
elif [ $ip_index = "local" ];then
if [ "$user" = "root" ];then
# ip=$(ifconfig $eth_card | grep 'inet6'| grep -v '::1'|grep -v 'fe80' | cut -f2 | awk '{ print $2}' | head -1)
ip=$(ifconfig $eth_card | awk '/inet6 24/' | awk '{print $2}' | head -1)
else
ip=$(/sbin/ifconfig $eth_card | grep 'inet6'| grep -v '::1'|grep -v 'fe80' | cut -f2 | awk '{ print $2}' | head -1)
fi
else
log "Error IP index, please input the right type"
exit 0
fi
fi
elif [ $record_type = "A" ];then
if [ "$reset_ip" -eq "1" ]; then
ip="127.0.0.1"
log "Reset DDNS address to loopback..."
elif [ "$reset_ip" -eq "2" ]; then
read -p "Please enter an A record IP:\n" ip
log "Got it, now setting DDNS..."
else
if [ $ip_index = "internet" ];then
ip=$(curl -4 ip.sb)
elif [ $ip_index = "local" ];then
if [ "$user" = "root" ];then
ip=$(ifconfig $eth_card | grep 'inet'| grep -v '127.0.0.1' | grep -v 'inet6'|cut -f2 | awk '{ print $2}')
else
ip=$(/sbin/ifconfig $eth_card | grep 'inet'| grep -v '127.0.0.1' | grep -v 'inet6'|cut -f2 | awk '{ print $2}')
fi
else
log "Error IP index, please input the right type"
exit 0
fi
fi
else
log "Error DNS type"
exit 0
fi

# SCRIPT START
log "Check Initiated"
log "Your ip is: ${ip}"

# 判断ip是否发生变化
if [ "$enable_cache" -eq "1" ]; then
if [ -f $ip_file ]; then
old_ip=$(cat $ip_file)
if [ $ip == $old_ip ]; then
log "IP has not changed."
exit 0
fi
fi
fi


# 更新DNS记录
update=$(curl --request PATCH \
--url "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records/$record_id" \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer $api_key" \
--data "{\"content\": \"$ip\", \"proxied\": $proxied, \"type\": \"$record_type\", \"comment\": \"DDNS\", \"ttl\": 1}")

echo "$update"



# 输出DDNS更新情况
if [[ $update == *"\"success\":true"* ]]; then
message="$api_key -> $ip"
if [ "$enable_cache" -eq "1" ]; then
echo "$message" > $ip_file
fi
log "$message"
else
message="API UPDATE FAILED. DUMPING RESULTS:\n$update"
log "$message"
exit 1
fi

这是python版的:

python版兼任linuxwindows,shell版适合性能差的设备。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
import requests
import os
import sys
import datetime
import platform
import subprocess
import re

# 需要先安装以下pip包才能运行:
# requests

# 配置变量
zone_id="" # cloudFlare里域名的zone id
record_id="" # 你要修改的二级域名的record id,通过get脚本获取
api_key="" # 你的API key,用于修改dns记录的key
domain_main="" # 你的主域名, 如google.com
proxied="false" # 是否要启用cdn。合法值只有true和false
record_type = "AAAA" # A or AAAA, ipv4 或 ipv6解析

eth_card = "" # 使用本地方式获取IP绑定的网卡, 如eth0。此设置项只在linux上生效
ip_index = "local" # 使用 "internet" 或 "local", 获取地址的方式

reset_ip = 0 # 1为将ddns的ip改为localhost,2为手动输入ip,其他任意值为不是
enable_cache = 0 # 1为保存缓存文件,其他任意值为不保存缓存文件
skip_connectivity_check = 0 # 0为不跳过脚本最开始运行的时候的检查连接
ip_file = "ip.txt" # 保存地址信息的文件
id_file = "cloudflare.ids"
log_file = "cloudflare.log"


# 按照这个格式填入config.txt
example_config_file = {
"zone_id": "", # cloudFlare里域名的zone id
"record_id": "", # 你要修改的二级域名的record id,通过get脚本获取
"api_key": "", # 你的API key,用于修改dns记录的key
"domain_main": "", # 你的主域名, 如google.com
"proxied": "false", # 是否要启用cdn。合法值只有true和false
"record_type": "", # A or AAAA, ipv4 或 ipv6解析
"eth_card": "", # 使用本地方式获取IP绑定的网卡, 如eth0。此设置项只在linux上生效
"ip_index": "local", # 使用 "internet" 或 "local", 获取地址的方式。internal不支持获取ipv4地址
"reset_ip": 0, # 1为将ddns的ip改为localhost,其他任意值为不是
"enable_cache": 0, # 1为保存缓存文件,其他任意值为不保存缓存文件
"skip_connectivity_check": 0,# 0为不跳过脚本最开始运行的时候的检查连接
"ip_file": "ip.txt", # 不需要填写
"id_file": "cloudflare.ids", # 不需要填写
"log_file": "cloudflare.log" # 不需要填写
}

def log(message):
"""日志函数"""
if message:
if enable_cache == 1:
with open(log_file, 'a') as f:
f.write(f"[{datetime.datetime.now()}] - {message}\n")
else:
print(f"[{datetime.datetime.now()}] - {message}")


def init_value():
global zone_id, record_id, api_key, domain_main, proxied, record_type, eth_card, skip_connectivity_check, ip_index, reset_ip, enable_cache, ip_file, id_file, og_file
config_dict = {}
script_dir = os.path.dirname(os.path.abspath(__file__))
config_path = os.path.join(script_dir, "config.txt")
try:
with open(config_path, 'r', encoding='utf-8') as file:
for line in file:
line = line.strip()
if line and '=' in line:
key, value = line.split('=', 1)
config_dict[key] = value
common_keys = set(config_dict.keys()) & set(example_config_file.keys())
different_values = {}
for key in common_keys:
if config_dict[key] != example_config_file[key]:
different_values[key] = (config_dict[key], example_config_file[key])
if len(different_values) == 0:
print(f"请将该配置文件填写完成:{config_path}")
sys.exit()
log("File config.txt exists.")
zone_id = config_dict.get('zone_id')
record_id = config_dict.get('record_id')
api_key = config_dict.get('api_key')
domain_main = config_dict.get('domain_main')
proxied = config_dict.get('proxied')
record_type = config_dict.get('record_type')
eth_card = config_dict.get('eth_card')
ip_index = config_dict.get('ip_index')
reset_ip = int(config_dict.get('reset_ip'))
enable_cache = int(config_dict.get('enable_cache'))
skip_connectivity_check = int(config_dict.get('skip_connectivity_check'))
ip_file = config_dict.get('ip_file')
id_file = config_dict.get('id_file')
log_file = config_dict.get('log_file')
except FileNotFoundError:
with open(config_path, 'w', encoding='utf-8') as file:
for key, value in example_config_file.items():
file.write(f'{key}={value}\n')
#json.dump(example_config_file, file, ensure_ascii=False, indent=4)
print(f"请将该配置文件填写完成:{config_path}")
sys.exit()

def get_ipv6_address_windows():
"""在Windows上获取临时IPv6地址"""
try:
# 执行ipconfig命令并捕获输出
output = subprocess.check_output(["ipconfig"], text=True)
# 使用正则表达式查找临时IPv6地址
match = re.search(r"临时 IPv6 地址.*: ([a-fA-F0-9:]+)", output)
if match:
return match.group(1)
else:
return None
except Exception as e:
print(f"Error: {e}")
return None

def get_ipv6_address_linux():
"""在Linux上获取Global IPv6地址"""
try:
# 执行ip -6 addr show命令并捕获输出
output = subprocess.check_output(["ip", "-6", "addr", "show", eth_card], text=True)
# 使用正则表达式查找global scope的IPv6地址
match = re.search(r"inet6\s+([a-fA-F0-9:]+)/\d+\s+scope global", output)
if match:
return match.group(1)
else:
return None
except Exception as e:
print(f"Error: {e}")
return None

def get_ipv6_platform():
if platform.system() == "Windows":
return get_ipv6_address_windows()
elif platform.system() == "Linux":
return get_ipv6_address_linux()
else:
return None

def get_ip():
"""获取IP地址"""
if record_type == "AAAA":
if reset_ip == 1:
log("Reset DDNS address to loopback...")
return "::1"
elif reset_ip == 2:
ip = input("Please enter an AAAA record IP:\n")
log("Got it, now setting DDNS...")
return ip
else:
if ip_index == "internet":
return requests.get('https://api64.ipify.org?format=json').json()['ip']
elif ip_index == "local":
return get_ipv6_platform()
pass
elif record_type == "A":
if reset_ip == 1:
log("Reset DDNS address to loopback...")
return "127.0.0.1"
elif reset_ip == 2:
ip = input("Please enter an A record IP:\n")
log("Got it, now setting DDNS...")
return ip
else:
if ip_index == "internet":
return requests.get('https://api.ipify.org?format=json').json()['ip']
elif ip_index == "local":
return input("输入你的ipv4地址\n")
pass
else:
log("Error DNS type")
exit(0)


"""更新Cloudflare DNS记录"""
def update_cloudflare_dns(ip):
# 更新DNS记录

proxied_tmp = False
if proxied == "true":
proxied_tmp = True

headers = {
"Authorization": "Bearer " + api_key,
"Content-Type": "application/json",
}
data = {
"content": ip,
"name": domain_main,
"proxied": proxied_tmp,
"type": record_type,
"comment": "DDNS",
"ttl": "1"
}
update_response = requests.patch("https://api.cloudflare.com/client/v4/zones/" + zone_id + "/dns_records/" + record_id, headers=headers, json=data)
return update_response.json()



# 主逻辑
log("Init config")
init_value()
log("Starting DDNS")
if skip_connectivity_check == 0:
# 检查连接性
log("Checking connectivity to cloudflare")
try:
response = requests.get("https://api.cloudflare.com", timeout=30)
# 如果请求成功,状态码为200
if response.ok:
log("Successfully connected to https://api.cloudflare.com")
else:
log("Connected to https://api.cloudflare.com but received a non-success status code:", response.status_code)
except requests.exceptions.RequestException as e:
# 处理连接错误
log("Error connecting to https://api.cloudflare.com")
log(e)
sys.exit()
except requests.exceptions.Timeout:
# 处理连接超时
log("Timeout connecting to https://api.cloudflare.com")
log(e)
sys.exit()
log("Check Initiated")
else:
log("Skipping check connectivity to Cloudflare")

# 获取IP地址
ip = get_ip()
log(f"Your ip is: {ip}")

# 判断IP是否发生变化
old_ip = None
if enable_cache == 1 and os.path.exists(ip_file):
with open(ip_file) as f:
old_ip = f.read().strip()
if ip == old_ip:
if ip == None:
log("Check your Internet connection.")
else:
log("IP has not changed.")
exit(0)

# 更新DNS记录
update_result = update_cloudflare_dns(ip)
if update_result['success']:
message = f"Now {record_id} -> {ip}"
if enable_cache == 1:
with open(ip_file, 'w') as f:
f.write(message)
log(message)
else:
message = f"API UPDATE FAILED. DUMPING RESULTS:\n{update_result}"
log(message)
exit(1)


折腾一番CloudFlare DDNS
http://blog.coolenoch.ink/2024/03/02/10折腾一番CloudFlare DDNS-240302/
作者
CoolestEnoch
发布于
2024年3月2日
许可协议