收起左侧

飞牛ddns功能有点儿弱

1
回复
75
查看
[ 复制链接 ]

2

主题

3

回帖

0

牛值

江湖小虾

目前飞牛的ddns只能更对当前电脑的ip地址做dns解析,有点太弱了,希望可以对局域网内的其它机器也可以做dns解析。支持可配置的地址获取方式,下面以cloudflare为例,说明一下期望的功能。

需要解析的域名配置

下面的数据存储到sqlite中,其中:

cf_identifier: cloudflare中的域名标识符号

hostname: 要获取哪个机器的ip地址

addr_type: 地址类型, A 表示ipv4, AAAA 表示解析ipv6

domain: 完整的域名,如fnos.fnnas.com

ip_addr: 实际获取到的ip地址, 后面通过接口将该地址解析到domain字段的域名

update_time: 记录更新时间

resolve_type: 可以扩展,现在以internet和resolve来举例说明:

  • internet 表示获取当前机器的出口公网ip地址
  • resolve 表示使用avahi-resolve命令,根据addr_type和hostname解析出ip地址
 
root@fnos:~# sqlite3 ddns.db 
SQLite version 3.40.1 2022-12-28 14:03:47
Enter ".help" for usage hints.
sqlite> .schema
CREATE TABLE ddns (
  id integer, 
  resolve_type text,
  hostname text,
  addr_type text,
  cf_identifier text,
  domain text,
  ip_addr text,
  update_time integer
);
sqlite> select * from ddns;
1|internet||A|8cc34d8d1d8420b48a23f355057698e6|fnos.masked.top|101.71.232.255|1769416996
2|internet||A|b42e573a2d52412513d3b73100fcf4c4|pub.masked.top|101.71.232.255|1769416998
3|resolve|server.local|AAAA|5260599072e57252425f262f249633e0|pc.masked.top|2408:8240:c11:56b0:0000:86ff:0000:f192|1769416999
4|resolve|fnos.local|AAAA|9361dc80027fe287dadd8c86e3604313|fnos.masked.top|fe80::3d45:0000:0000:12f5|1769658837

最终实现的效果就是,根据hostname、addr_type、resolve_type解析出一个ip地址,调用cloudflare接口进行更新。并把解析出来的ip地址,更新到ip_addr字段,下次定时任务执行时,检查是否有变更,只有在有变更时才需要进行更新。

基于脚本实现的方案

以下脚本由千问大模型生成,仅对更新ip_addr和update_time字段部分做了错误修正,实测功能是正常的,使用systemd的定时器,每10分钟检测一次,可以实现预期效果。


root@fnos:~# cat ddns.sh 
#!/bin/bash

set -euo pipefail

# ================== 配置区 ==================
CF_ZONE_ID=05049340000000000000000000435093
CF_AUTH_KEY=04bdd80000000000000000000fb9003c3c4a8
CF_AUTH_EMAIL=use@someserver.com
DB_FILE="/root/ddns.db"

ZONE_ID="${CF_ZONE_ID:-}"
AUTH_EMAIL="${CF_AUTH_EMAIL:-}"
AUTH_KEY="${CF_AUTH_KEY:-}"

# 从配置文件加载(可选)
if [ -z "$ZONE_ID" ] || [ -z "$AUTH_EMAIL" ] || [ -z "$AUTH_KEY" ]; then
    if [ -f ~/.cf_ddns.conf ]; then
        source ~/.cf_ddns.conf
    fi
fi

# ================== 函数定义 ==================

# 获取当前公网 IP
get_current_public_ip() {
    local ip_type=${1:-ipv4}
    local ip=""
    if [ "$ip_type" = "ipv4" ]; then
        ip=$(curl -s --max-time 10 -4 http://4.ipw.cn || curl -s --max-time 10 -4 https://api.ipify.org)
    elif [ "$ip_type" = "ipv6" ]; then
        ip=$(curl -s --max-time 10 -6 http://6.ipw.cn || curl -s --max-time 10 -6 https://api6.ipify.org)
    else
        echo "Unknown IP type: $ip_type" >&2
        return 1
    fi

    if [ -z "$ip" ]; then
        echo "Failed to fetch public IP of type $ip_type" >&2
        return 1
    fi

    echo "$ip"
}

# 解析本地主机名 IP(使用 avahi-resolve)
resolve_hostname_to_ip() {
    local hostname="$1"
    local addr_type="$2"  # A or AAAA

    # 检查 avahi-resolve 是否可用
    if ! command -v avahi-resolve >/dev/null 2>&1; then
        echo "Error: avahi-resolve command not found." >&2
        return 1
    fi

    # 构建查询类型参数
    local resolve_flags="-4"
    if [ "$addr_type" = "AAAA" ]; then
        resolve_flags="-6"
    fi

    # 执行解析
    local ip
    ip=$(avahi-resolve $resolve_flags -n "$hostname" 2>/dev/null | awk '{print $2}' | head -n1)
    if [ -z "$ip" ]; then
        echo "Failed to resolve hostname $hostname for $addr_type" >&2
        return 1
    fi

    echo "$ip"
}

# 更新 Cloudflare DNS 记录
update_cloudflare_record() {
    local domain="$1"
    local identifier="$2"
    local new_ip="$3"
    local record_type="$4"

    echo "[$(date '+%Y-%m-%d %H:%M:%S')] Updating $domain ($record_type) to $new_ip..."

    local response
    response=$(curl -sS --max-time 30 -X PUT \
        "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records/${identifier}" \
        --header "X-Auth-Email: ${AUTH_EMAIL}" \
        --header "X-Auth-Key: ${AUTH_KEY}" \
        --header "Content-Type: application/json" \
        --data "{\"content\":\"${new_ip}\",\"name\":\"${domain}\",\"proxied\":false,\"type\":\"${record_type}\",\"ttl\":120}" \
        2>&1)

    if [ $? -ne 0 ]; then
        echo "[$(date '+%Y-%m-%d %H:%M:%S')] Error: curl failed: $response" >&2
        return 1
    fi

    # 检查 API 返回是否成功
    if echo "$response" | grep -q '"success":true'; then
        echo "[$(date '+%Y-%m-%d %H:%M:%S')] Successfully updated DNS record for $domain"
        return 0
    else
        echo "[$(date '+%Y-%m-%d %H:%M:%S')] API error: $(echo "$response" | jq -r '.errors // empty' 2>/dev/null || echo "$response")" >&2
        return 1
    fi
}

# 更新数据库中的 ip_addr 和 update_time
update_db_record() {
    local id="$1"
    local new_ip="$2"
    local timestamp="$3"

    sqlite3 $DB_FILE "update ddns set ip_addr = '$new_ip', update_time=$timestamp where id = $id;"
    #"printf '%s\n%s\n%s' "$new_ip" "$timestamp" "$id" | sqlite3 "$DB_FILE" "UPDATE ddns SET ip_addr = ?, update_time = ? WHERE id = ?;"
}

# ================== 主逻辑 ==================

echo "[$(date '+%Y-%m-%d %H:%M:%S')] Starting DDNS update process..."

# 从数据库中查询所有记录
while IFS='|' read -r id resolve_type hostname domain addr_type cf_identifier ip_addr _; do
    echo "Processing ID $id: $domain ($addr_type)"

    # 根据 resolve_type 获取 IP
    if [ "$resolve_type" = "internet" ]; then
        if [ "$addr_type" = "A" ]; then
            current_ip=$(get_current_public_ip ipv4) || continue
        elif [ "$addr_type" = "AAAA" ]; then
            current_ip=$(get_current_public_ip ipv6) || continue
        else
            echo "Unsupported addr_type $addr_type for internet resolve_type" >&2
            continue
        fi
    elif [ "$resolve_type" = "resolve" ]; then
        if [ -z "$hostname" ]; then
            echo "Error: hostname field is empty for resolve_type 'resolve' (ID: $id)" >&2
            continue
        fi
        current_ip=$(resolve_hostname_to_ip "$hostname" "$addr_type") || continue
    else
        echo "Unknown resolve_type: $resolve_type (ID: $id)" >&2
        continue
    fi

    # 比较新 IP 与数据库中的旧 IP
    if [ "x$ip_addr" = "x$current_ip" ]; then
        echo "[$(date '+%Y-%m-%d %H:%M:%S')] IP for $domain unchanged ($current_ip). Skipping update."
        continue
    fi

    # IP 已改变,调用 Cloudflare API 更新
    if update_cloudflare_record "$domain" "$cf_identifier" "$current_ip" "$addr_type"; then
        # 更新数据库记录
        now_timestamp=$(date +%s)
        update_db_record "$id" "$current_ip" "$now_timestamp"
        echo "[$(date '+%Y-%m-%d %H:%M:%S')] Database record for ID $id updated with IP $current_ip."
    else
        echo "[$(date '+%Y-%m-%d %H:%M:%S')] Failed to update DNS for $domain (ID: $id). DB not updated." >&2
    fi

done < <(sqlite3 -separator '|' "$DB_FILE" "SELECT id, resolve_type, hostname, domain, addr_type, cf_identifier, ip_addr FROM ddns;")

echo "[$(date '+%Y-%m-%d %H:%M:%S')] DDNS update process completed."
bash

root@fnos:~# systemctl cat ddns.timer 
# /lib/systemd/system/ddns.timer

[Unit]
Description=Run DDNS Update

[Timer]
OnActiveSec=10
OnBootSec=30
OnUnitActiveSec=600
Unit=ddns.service

[Install]
WantedBy=multi-user.target




root@fnos:~# systemctl cat ddns.service 
# /lib/systemd/system/ddns.service
[Unit]
Description=Cloudflare ddns ipv6 for local nas

[Service]
Type=oneshot
User=root
ExecStart=/root/nas_ddns.sh

[Install]
WantedBy=multi-user.target
Also=ddns.timer

执行日志信息

maskedroot@fnos:~# journalctl -u ddns  -n 12
1月 31 13:43:58 fnos nas_ddns.sh[22997]: [2026-01-31 13:43:58] Starting DDNS update process...
1月 31 13:43:58 fnos nas_ddns.sh[22997]: Processing ID 1: fnos.masked.top (A)
1月 31 13:43:58 fnos nas_ddns.sh[22997]: [2026-01-31 13:43:58] IP for fnos.masked.top unchanged (101.71.232.255). Skipping update.
1月 31 13:43:58 fnos nas_ddns.sh[22997]: Processing ID 2: pub.masked.top (A)
1月 31 13:43:58 fnos nas_ddns.sh[22997]: [2026-01-31 13:43:58] IP for pub.masked.top unchanged (101.71.232.255). Skipping update.
1月 31 13:43:58 fnos nas_ddns.sh[22997]: Processing ID 3: pc.masked.top (AAAA)
1月 31 13:44:03 fnos nas_ddns.sh[23011]: Failed to resolve hostname server.local for AAAA
1月 31 13:44:03 fnos nas_ddns.sh[22997]: Processing ID 4: fnos.masked.top (AAAA)
1月 31 13:44:03 fnos nas_ddns.sh[22997]: [2026-01-31 13:44:03] IP for fnos.masked.top unchanged (fe80::3d45:55e9:8cd7:12f5). Skipping update.
1月 31 13:44:03 fnos nas_ddns.sh[22997]: [2026-01-31 13:44:03] DDNS update process completed.
1月 31 13:44:03 fnos systemd[1]: ddns.service: Deactivated successfully.
1月 31 13:44:03 fnos systemd[1]: Finished ddns.service - Cloudflare ddns ipv6 for local nas.

对个人来说,这么处理已经完全可以满足需要了,没必要再要求飞牛实现这样的功能,只是觉得飞牛立志做一个伟大的产品,目前的方案是很大众的解决方式,没有办法解决文中描述的这种需求,完全可以做的更好,如果能集成的官方系统中,感觉肯定会更好。

另外,个人感觉在飞牛中删除域名解析配置时,没必要调用服务商接口把域名也删掉,或者可以加个配置开关,用户来决定是否同步删除。 本来我用脚本做ddns的, 试了一下飞牛的ddns,结果在飞牛中删除后,发现脚本解析也失败了,登录cloudflare一看,发现那条域名解析也被删掉了。

收藏
送赞
分享

6

主题

1万

回帖

0

牛值

管理员

社区上线纪念勋章社区共建团荣誉勋章飞牛百度网盘玩家fnOS1.0上线纪念勋章

感谢反馈,这个需求先记录下来,我们会根据评估结果推进

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则