【iStoreOS】OpenWRT 22.03.7中IPv6地址变动BUG及临时解决方案(适用于多WAN环境)

背景


在使用OpenWRT 22.03.7系统叠加MWAN3多线负载均衡状况下,双线IPv6地址在正常使用一段时间后,LAN口侧会无形中增加许多IPv6的地址(很多是IPv6-PD前缀已丢弃,但LAN侧未释放问题)导致IPv6中断无法使用。


网络和配置环境


测试系统:iStore OS 22.03.7 2025101516

网络拨号环境:光猫桥接,PPPoE拨号,DHCPv6客户端(@{IPv4拨号接口})


前景铺垫


在网络检索过程中,发现此问题为OpenWRT 22.03通病,在OpenWRT 23.x版本序列中部分修复,OpenWRT 24.x版本序列中完全修复。

结合回答问题推测,核心问题根因

核心问题引起点在odhcpd中,在对iStore OS的代码合并中也发现,iStore OS合并了大量odhcpd相关的修复,但此修复未应用在OpenWRT22.03.x版本序列中。


修复方案


方案1:鉴于OpenWRT官方真的odhcpd做了大量修复及检索关系分析排查,问题大概率出在odhcpd中,因此将odhcpd相关修复代码合并到OpenWRT22.03.x版本序列中应可修复此问题。

方案2:鉴于论坛中已有相类似案例,来源:路由器IPV6地址变动导致wan6掉pd解决方法。结合此方案脚本和本人测试,当IPv6掉网情况下,通过重启虚拟的DHCPv6接口,可恢复网络功能。

因此,编制了一个脚本来实现自动化处理。此脚本支持自己分析IPv6所对应的接口,无需指定,加入了锁机制和一些其它逻辑(感谢AI的DECODE)。脚本可能还存在BUG和意想不到的问题,大家自行选择尝试。适用于单WAN、多WAN等情况,单WAN建议使用论坛中其它佬的版本更简化。

最终。食用方法为在计划任务中添加任务即可。

相关脚本如下:

#!/bin/sh
PATH=/sbin:/usr/sbin:/bin:/usr/bin
[ -r "$0" ] && sed -i 's/\r$//' "$0" 2>/dev/null
. /lib/functions/network.sh 2>/dev/null || true
STATE_FILE="/tmp/last_ipv6_pd_strict"
log() { ts=$(date '+%Y-%m-%d %H:%M:%S'); echo "$ts ipv6pdcheck $*"; if command -v logger >/dev/null 2>&1; then logger -t ipv6pdcheck "$*"; fi; }
LOCK_BASE="/tmp/ipv6pd.lock"
LOCK_ID="$$"
MASTER_LOCK="/tmp/ipv6pd.master.lock"
acquire_master_lock() {
 pdir=$(dirname "$MASTER_LOCK")
 mkdir -p "$pdir" 2>/dev/null
 log "lock_master_try id=$LOCK_ID dir=$MASTER_LOCK"
 if mkdir "$MASTER_LOCK" 2>/dev/null; then
  ts=$(date +%s)
  echo "$ts" > "$MASTER_LOCK/ts"
  echo "$LOCK_ID" > "$MASTER_LOCK/id"
  log "lock_master_acquired id=$LOCK_ID ts=$ts dir=$MASTER_LOCK"
  return 0
 fi
 if [ -f "$MASTER_LOCK/id" ]; then
  hid=$(cat "$MASTER_LOCK/id" 2>/dev/null)
  if kill -0 "$hid" 2>/dev/null; then
   log "lock_master_busy holder_id=$hid dir=$MASTER_LOCK"
   return 1
  else
   log "lock_master_stale holder_id=$hid dir=$MASTER_LOCK"
   rm -rf "$MASTER_LOCK"
   mkdir "$MASTER_LOCK" 2>/dev/null || return 1
   ts=$(date +%s)
   echo "$ts" > "$MASTER_LOCK/ts"
   echo "$LOCK_ID" > "$MASTER_LOCK/id"
   log "lock_master_reacquired id=$LOCK_ID ts=$ts dir=$MASTER_LOCK"
   return 0
  fi
 fi
 return 1
}
release_master_lock() { log "lock_master_release id=$LOCK_ID dir=$MASTER_LOCK"; rm -rf "$MASTER_LOCK"; }
get_cooldown() { i="$1"; case "$i" in lan) echo 3;; *) echo 5;; esac; }
get_stale_sec() { i="$1"; case "$i" in lan) echo 0;; *) echo 10;; esac; }
acquire_lock_for() {
 i="$1"
 LD="$LOCK_BASE.$i"
 pdir=$(dirname "$LD")
 mkdir -p "$pdir" 2>/dev/null
 log "lock_try id=$LOCK_ID iface=$i dir=$LD"
 if mkdir "$LD" 2>/dev/null; then
  ts=$(date +%s)
  echo "$ts" > "$LD/ts"
  echo "$LOCK_ID" > "$LD/id"
  log "lock_acquired id=$LOCK_ID ts=$ts iface=$i dir=$LD"
  return 0
 fi
 if [ -f "$LD/ts" ]; then
  now=$(date +%s)
  ts=$(cat "$LD/ts")
  age=$((now - ts))
  STALE=$(get_stale_sec "$i")
  if [ "$age" -gt "$STALE" ]; then
   log "lock_stale_remove ts=$ts age=$age now=$now iface=$i dir=$LD"
   rm -rf "$LD"
   mkdir "$LD" 2>/dev/null || return 1
   nts=$(date +%s)
   echo "$nts" > "$LD/ts"
   echo "$LOCK_ID" > "$LD/id"
   log "lock_reacquired id=$LOCK_ID ts=$nts iface=$i dir=$LD"
   return 0
  else
   hid=$(cat "$LD/id" 2>/dev/null)
   if kill -0 "$hid" 2>/dev/null; then
    log "lock_busy holder_id=$hid ts=$ts age=$age iface=$i dir=$LD"
   else
    log "lock_stale_remove_dead holder_id=$hid ts=$ts age=$age iface=$i dir=$LD"
    rm -rf "$LD"
    mkdir "$LD" 2>/dev/null || return 1
    nts=$(date +%s)
    echo "$nts" > "$LD/ts"
    echo "$LOCK_ID" > "$LD/id"
    log "lock_reacquired id=$LOCK_ID ts=$nts iface=$i dir=$LD"
    return 0
   fi
  fi
 else
  log "lock_busy_no_ts iface=$i dir=$LD"
 fi
 return 1
}
release_lock_for() {
 i="$1"
 LD="$LOCK_BASE.$i"
 log "lock_release id=$LOCK_ID iface=$i dir=$LD"
 rm -rf "$LD"
}

if ! acquire_master_lock; then log "instance_running_master"; exit 0; fi
trap 'release_master_lock' EXIT

# Enforce a global cooldown to prevent flapping
GLOBAL_COOLDOWN_FILE="/tmp/ipv6pd_global_cooldown"
GLOBAL_COOLDOWN_SECONDS=55 # Almost a minute

# Check for global cooldown after acquiring master lock
if [ -f "$GLOBAL_COOLDOWN_FILE" ]; then
    last_run_ts=$(cat "$GLOBAL_COOLDOWN_FILE" 2>/dev/null)
    if [ -n "$last_run_ts" ]; then
        now_ts=$(date +%s)
        age=$((now_ts - last_run_ts))
        if [ "$age" -lt "$GLOBAL_COOLDOWN_SECONDS" ]; then
            log "global_cooldown_active age=$age, skipping run"
            exit 0
        fi
    fi
fi

# Update the global cooldown timestamp at the beginning of a valid run
mkdir -p "$(dirname "$GLOBAL_COOLDOWN_FILE")" 2>/dev/null
date +%s > "$GLOBAL_COOLDOWN_FILE"


IFACES=$(ubus list network.interface.* | sed 's/network.interface.//')
DIALS=""
for i in $IFACES; do
 s=$(ubus call network.interface.$i status)
 p=$(echo "$s" | jsonfilter -e '@["proto"]')
 if [ "$p" = "dhcpv6" ]; then
  DIALS="$DIALS $i"
 fi
done
[ -z "$DIALS" ] && log "no_dial_ifaces"
type network_flush_cache >/dev/null 2>&1 && network_flush_cache || true
if type network_get_device >/dev/null 2>&1; then network_get_device LAN_DEV "lan"; fi
[ -z "$LAN_DEV" ] && LAN_DEV=$(ubus call network.interface.lan status | jsonfilter -e '@["l3_device"]')
[ -z "$LAN_DEV" ] && log "lan_dev_not_found" && exit 0
ipv6_to_hex() { awk -v ip="$1" 'function pad4(s){s=tolower(s);n=length(s);if(n==0)return "0000";if(n==1)return "000" s;if(n==2)return "00" s;if(n==3)return "0" s;return s} BEGIN{ split(ip,a,"/"); ip=a[1]; gsub(/[[:space:]]/,"",ip); if (index(ip,"::")){ split(ip,parts,"::"); left=parts[1]; right=parts[2]; lc=split(left,l,":"); if (right!="") rc=split(right,r,":"); else rc=0; } else { left=ip; lc=split(left,l,":"); rc=0; } missing=8-lc-rc; res=""; for(i=1;i<=lc;i++) res=res pad4(l[i]); for(i=1;i<=missing;i++) res=res "0000"; for(i=1;i<=rc;i++) res=res pad4(r[i]); print res; }'; }
h2d() { case "$1" in 0) echo 0;; 1) echo 1;; 2) echo 2;; 3) echo 3;; 4) echo 4;; 5) echo 5;; 6) echo 6;; 7) echo 7;; 8) echo 8;; 9) echo 9;; a|A) echo 10;; b|B) echo 11;; c|C) echo 12;; d|D) echo 13;; e|E) echo 14;; f|F) echo 15;; *) echo 0;; esac; }
norm_list() { echo "$1" | tr ' ' '\n' | sed '/^$/d' | sort | tr '\n' ' ' | sed 's/[[:space:]]*$//'; }
uniq_list() { echo "$1" | tr ' ' '\n' | sed '/^$/d' | awk '!seen[$0]++' | tr '\n' ' ' | sed 's/[[:space:]]*$//'; }
prefix_match() {
 addr="$1"; pd="$2"; mask="$3"
 ahex=$(ipv6_to_hex "$addr")
 phex=$(ipv6_to_hex "$pd")
 n=$((mask/4)); r=$((mask%4))
 apre=$(echo "$ahex" | cut -c 1-$n)
 ppre=$(echo "$phex" | cut -c 1-$n)
 [ "$apre" != "$ppre" ] && echo 0 && return
 if [ "$r" -eq 0 ]; then echo 1; return; fi
 an=$(echo "$ahex" | cut -c $((n+1))-$((n+1)))
 pn=$(echo "$phex" | cut -c $((n+1))-$((n+1)))
 av=$(h2d "$an"); pv=$(h2d "$pn")
 shift=$((4 - r))
 if [ "$shift" -eq 1 ]; then div=2; elif [ "$shift" -eq 2 ]; then div=4; elif [ "$shift" -eq 3 ]; then div=8; else div=1; fi
 [ $((pv / div)) -eq $((av / div)) ] && echo 1 || echo 0
}
match_any() {
 addr="$1"; res=0
 for pc in $PD_LIST; do
  pa=${pc%/*}; pm=${pc#*/}
  m=$(prefix_match "$addr" "$pa" "$pm")
  [ "$m" = "1" ] && res=1 && break
 done
 echo $res
}
log "lan_dev=$LAN_DEV"
s_lan=$(ubus call network.interface.lan status)
LAN64_KEYS=""
LAN64_UNIQ_ADDRS=""
ULA64_KEYS=""
ULA64_UNIQ_ADDRS=""
 for idx in 0 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; do
 a=$(echo "$s_lan" | jsonfilter -e "@[\"ipv6-address\"][${idx}][\"address\"]")
 m=$(echo "$s_lan" | jsonfilter -e "@[\"ipv6-address\"][${idx}][\"mask\"]")
 [ -z "$a" ] && continue
 [ "$m" != "64" ] && continue
 case "$a" in fe80:*) continue;; esac
 hex=$(ipv6_to_hex "$a")
 key=$(echo "$hex" | cut -c 1-16)
 case "$a" in fd*|fc*)
  exist=0
  for k in $ULA64_KEYS; do [ "$k" = "$key" ] && exist=1 && break; done
  [ "$exist" -eq 0 ] && ULA64_KEYS="$ULA64_KEYS $key" && ULA64_UNIQ_ADDRS="$ULA64_UNIQ_ADDRS $a"
  ;;
 *)
  exist=0
  for k in $LAN64_KEYS; do [ "$k" = "$key" ] && exist=1 && break; done
  [ "$exist" -eq 0 ] && LAN64_KEYS="$LAN64_KEYS $key" && LAN64_UNIQ_ADDRS="$LAN64_UNIQ_ADDRS $a"
  ;;
 esac
done
LIST=$(ip -6 addr show dev "$LAN_DEV" | awk '/inet6 /{print $2}')
for ent in $LIST; do
 addr=${ent%/*}; mask=${ent#*/}
 [ -z "$addr" ] && continue
 [ "$mask" != "64" ] && continue
 case "$addr" in fe80:*) continue;; esac
 hex=$(ipv6_to_hex "$addr")
 key=$(echo "$hex" | cut -c 1-16)
 case "$addr" in fd*|fc*)
  exist=0
  for k in $ULA64_KEYS; do [ "$k" = "$key" ] && exist=1 && break; done
  [ "$exist" -eq 0 ] && ULA64_KEYS="$ULA64_KEYS $key" && ULA64_UNIQ_ADDRS="$ULA64_UNIQ_ADDRS $addr"
  ;;
 *)
  exist=0
  for k in $LAN64_KEYS; do [ "$k" = "$key" ] && exist=1 && break; done
  [ "$exist" -eq 0 ] && LAN64_KEYS="$LAN64_KEYS $key" && LAN64_UNIQ_ADDRS="$LAN64_UNIQ_ADDRS $addr"
  ;;
 esac
done
LAN64_COUNT=$(echo "$LAN64_UNIQ_ADDRS" | sed '/^$/d' | wc -l)
ULA64_COUNT=$(echo "$ULA64_UNIQ_ADDRS" | sed '/^$/d' | wc -l)
log "lan64_count=$LAN64_COUNT"
log "lan64_ula_count=$ULA64_COUNT"
PD_LIST=""
ASSIGNED64_COUNT=0
ASSIGNED_KEYS=""
PD_GROUPS=""
PD_NORM_KEYS=""
for i in $DIALS; do
 s=$(ubus call network.interface.$i status)
 for idx in 0 1 2 3 4 5 6 7 8 9; do
  A=$(echo "$s" | jsonfilter -e "@[\"ipv6-prefix\"][${idx}][\"address\"]")
  M=$(echo "$s" | jsonfilter -e "@[\"ipv6-prefix\"][${idx}][\"mask\"]")
  [ -z "$A" ] && continue
  [ -z "$M" ] && continue
  pair="$A/$M"
  exist=0
  for e in $PD_LIST; do [ "$e" = "$pair" ] && exist=1 && break; done
  [ "$exist" -eq 0 ] && PD_LIST="$PD_LIST $pair"
  phex=$(ipv6_to_hex "$A")
  norm="$phex/$M"
  ng=0
  for nk in $PD_NORM_KEYS; do [ "$nk" = "$norm" ] && ng=1 && break; done
  if [ "$ng" -eq 0 ]; then
   PD_NORM_KEYS="$PD_NORM_KEYS $norm"
   PD_GROUPS="$PD_GROUPS $norm@$i"
  fi
 done
 for j in 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15; do
  PA=$(echo "$s" | jsonfilter -e "@[\"ipv6-prefix-assignment\"][${j}][\"address\"]")
  PM=$(echo "$s" | jsonfilter -e "@[\"ipv6-prefix-assignment\"][${j}][\"mask\"]")
  [ -z "$PA" ] && continue
  [ "$PM" != "64" ] && continue
  h=$(ipv6_to_hex "$PA")
  k=$(echo "$h" | cut -c 1-16)
  ex=0
  for kk in $ASSIGNED_KEYS; do [ "$kk" = "$k" ] && ex=1 && break; done
  if [ "$ex" -eq 0 ]; then ASSIGNED_KEYS="$ASSIGNED_KEYS $k"; ASSIGNED64_COUNT=$((ASSIGNED64_COUNT + 1)); fi
 done
done
if [ -z "$PD_LIST" ]; then
 NEW_STATE="$LAN64_COUNT $ASSIGNED64_COUNT 0 $PD_LIST"
 PREV_STATE=$(cat "$STATE_FILE" 2>/dev/null)
 log "pd_list_empty"
 mkdir -p "$(dirname "$STATE_FILE")" 2>/dev/null
 echo "$NEW_STATE" > "$STATE_FILE"
 log "state_updated"
 exit 0
fi
log "dial_ifaces=$DIALS pd_list=$PD_LIST assigned64_count=$ASSIGNED64_COUNT"
log "pd_groups=$PD_GROUPS"
iface_pd_present() {
 s=$(ubus call network.interface.$1 status)
 a=$(echo "$s" | jsonfilter -e '@["ipv6-prefix"][*]["address"]' | sed '/^$/d' | wc -l)
 [ "$a" -gt 0 ] && echo 1 || echo 0
}
can_restart_iface() {
 i="$1"
 f="/tmp/ipv6pd.cooldown.$i"
 now=$(date +%s)
 if [ -f "$f" ]; then
  last=$(cat "$f")
  diff=$((now - last))
  CD=$(get_cooldown "$i")
  [ "$diff" -lt "$CD" ] && echo 0 && return
 fi
 echo 1
}
update_cooldown() { i="$1"; mkdir -p /tmp 2>/dev/null; date +%s > "/tmp/ipv6pd.cooldown.$i"; }
refresh_pd_info() {
PD_LIST=""
ASSIGNED64_COUNT=0
ASSIGNED_KEYS=""
PD_GROUPS=""
PD_NORM_KEYS=""
 for i in $DIALS; do
  s=$(ubus call network.interface.$i status)
  for idx in 0 1 2 3 4 5 6 7 8 9; do
   A=$(echo "$s" | jsonfilter -e "@[\"ipv6-prefix\"][${idx}][\"address\"]")
   M=$(echo "$s" | jsonfilter -e "@[\"ipv6-prefix\"][${idx}][\"mask\"]")
   [ -z "$A" ] && continue
   [ -z "$M" ] && continue
   pair="$A/$M"
   exist=0
   for e in $PD_LIST; do [ "$e" = "$pair" ] && exist=1 && break; done
   [ "$exist" -eq 0 ] && PD_LIST="$PD_LIST $pair"
   phex=$(ipv6_to_hex "$A")
   norm="$phex/$M"
   ng=0
   for nk in $PD_NORM_KEYS; do [ "$nk" = "$norm" ] && ng=1 && break; done
   if [ "$ng" -eq 0 ]; then
    PD_NORM_KEYS="$PD_NORM_KEYS $norm"
    PD_GROUPS="$PD_GROUPS $norm@$i"
   fi
  done
  for j in 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15; do
   PA=$(echo "$s" | jsonfilter -e "@[\"ipv6-prefix-assignment\"][${j}][\"address\"]")
   PM=$(echo "$s" | jsonfilter -e "@[\"ipv6-prefix-assignment\"][${j}][\"mask\"]")
   [ -z "$PA" ] && continue
   [ "$PM" != "64" ] && continue
   h=$(ipv6_to_hex "$PA")
   k=$(echo "$h" | cut -c 1-16)
   ex=0
   for kk in $ASSIGNED_KEYS; do [ "$kk" = "$k" ] && ex=1 && break; done
   if [ "$ex" -eq 0 ]; then ASSIGNED_KEYS="$ASSIGNED_KEYS $k"; ASSIGNED64_COUNT=$((ASSIGNED64_COUNT + 1)); fi
  done
 done
}
cleanup_lan_nonmatching() {
 [ -z "$PD_LIST" ] && return
 LIST=$(ip -6 addr show dev "$LAN_DEV" | awk '/inet6 /{print $2}')
 for ent in $LIST; do
  addr=${ent%/*}; mask=${ent#*/}
  [ -z "$addr" ] && continue
  [ "$mask" != "64" ] && continue
  case "$addr" in fe80:*|fd*|fc*) continue;; esac
  mm=$(match_any "$addr")
  if [ "$mm" != "1" ]; then
   ip -6 addr del "$addr/$mask" dev "$LAN_DEV" 2>/dev/null
   log "lan_addr_removed $addr/$mask"
  fi
 done
}
lan_gua_limit() { di=$(echo "$DIALS" | tr ' ' '\n' | sed '/^$/d' | wc -l); [ "$di" -lt 1 ] && di=1; echo "$di"; }
prune_excess_lan_gua() {
 limit=$(lan_gua_limit)
 c=0
 LIST=$(ip -6 addr show dev "$LAN_DEV" | awk '/inet6 /{print $2}')
 for ent in $LIST; do
  addr=${ent%/*}; mask=${ent#*/}
  [ -z "$addr" ] && continue
  [ "$mask" != "64" ] && continue
  case "$addr" in fe80:*|fd*|fc*) continue;; esac
  mm=$(match_any "$addr")
  [ "$mm" != "1" ] && continue
  c=$((c+1))
  if [ "$c" -gt "$limit" ]; then
   ip -6 addr del "$addr/$mask" dev "$LAN_DEV" 2>/dev/null
   log "lan_addr_pruned $addr/$mask"
  fi
 done
}
recalc_lan_counts() {
 LAN64_KEYS=""; LAN64_UNIQ_ADDRS=""; ULA64_KEYS=""; ULA64_UNIQ_ADDRS=""
 LIST=$(ip -6 addr show dev "$LAN_DEV" | awk '/inet6 /{print $2}')
 for ent in $LIST; do
  addr=${ent%/*}; mask=${ent#*/}
  [ -z "$addr" ] && continue
  [ "$mask" != "64" ] && continue
  case "$addr" in fe80:*) continue;; esac
  hex=$(ipv6_to_hex "$addr")
  key=$(echo "$hex" | cut -c 1-16)
  case "$addr" in fd*|fc*)
   exist=0
   for k in $ULA64_KEYS; do [ "$k" = "$key" ] && exist=1 && break; done
   [ "$exist" -eq 0 ] && ULA64_KEYS="$ULA64_KEYS $key" && ULA64_UNIQ_ADDRS="$ULA64_UNIQ_ADDRS $addr"
   ;;
  *)
   exist=0
   for k in $LAN64_KEYS; do [ "$k" = "$key" ] && exist=1 && break; done
   [ "$exist" -eq 0 ] && LAN64_KEYS="$LAN64_KEYS $key" && LAN64_UNIQ_ADDRS="$LAN64_UNIQ_ADDRS $addr"
   ;;
  esac
 done
 LAN64_COUNT=$(echo "$LAN64_UNIQ_ADDRS" | sed '/^$/d' | wc -l)
 ULA64_COUNT=$(echo "$ULA64_UNIQ_ADDRS" | sed '/^$/d' | wc -l)
}
restart_and_check_iface() {
 i="$1"
 cr=$(can_restart_iface "$i")
 if [ "$cr" != "1" ]; then
  log "iface=$i cooldown_skip"
  return 0
 fi
 attempt=1
 max_attempt=3
 while [ $attempt -le $max_attempt ]; do
  log "iface=$i restart_attempt=$attempt"
  update_cooldown "$i"
  ifdown "$i"
  sleep 1
  ifup "$i"
  t=0
  present=0
  while [ $t -lt 10 ]; do
   t=$((t+1))
   sleep 2
   pm=$(iface_pd_present "$i")
   if [ "$pm" = "1" ]; then present=1; break; fi
  done
  if [ "$present" = "1" ]; then
   log "iface=$i pd_present"
   break
  else
   log "iface=$i pd_missing_after_wait"
  fi
  attempt=$((attempt+1))
 done
 if [ "$present" != "1" ]; then
  log "iface=$i pd_missing_final"
   return 1
 fi
  return 0
}
MISMATCH=0
for la in $LAN64_UNIQ_ADDRS; do
 mm=$(match_any "$la")
 [ "$mm" != "1" ] && MISMATCH=1 && break
done
RESTART_ALL=0
[ "$MISMATCH" -eq 1 ] && RESTART_ALL=1
REASON="none"
[ "$MISMATCH" -eq 1 ] && REASON="mismatch"
log "precheck mismatch=$MISMATCH lan64=$LAN64_COUNT assigned=$ASSIGNED64_COUNT reason=$REASON"
PREV_STATE=$(cat "$STATE_FILE" 2>/dev/null)
PREV_PD_LIST=$(echo "$PREV_STATE" | cut -d ' ' -f 4-)
CUR_PD_NORM=$(norm_list "$PD_LIST")
PREV_PD_NORM=$(norm_list "$PREV_PD_LIST")
PD_CHANGE=0
[ "$CUR_PD_NORM" != "$PREV_PD_NORM" ] && PD_CHANGE=1
if [ "$PD_CHANGE" -eq 1 ]; then
 [ "$REASON" = "none" ] && REASON="pd_change"
 RESTART_ALL=1
fi
NEW_STATE="$LAN64_COUNT $ASSIGNED64_COUNT $MISMATCH $PD_LIST"
if [ "$PREV_STATE" != "$NEW_STATE" ]; then
 log "state_change_detected prev=$PREV_STATE new=$NEW_STATE reason=$REASON pd_change=$PD_CHANGE"
 if [ "$RESTART_ALL" -eq 1 ] && [ "$PD_CHANGE" -eq 1 ]; then
  log "lock_force_delete_all base=$LOCK_BASE"
  rm -rf ${LOCK_BASE}.* 2>/dev/null
 fi
fi
prev_assigned=$(echo "$PREV_STATE" | awk '{print $2}')
if [ "$RESTART_ALL" -ne 1 ] && [ "$ASSIGNED64_COUNT" -eq 0 ] && [ "$prev_assigned" = "0" ] && [ -n "$PD_LIST" ] && [ "$LAN64_COUNT" -eq 0 ]; then
 log "force_restart_due_to_assign_missing_persistent"
 for i in $DIALS; do
  if ! acquire_lock_for "$i"; then
   log "iface=$i instance_running"
   continue
  fi
  if ! restart_and_check_iface "$i"; then
   release_lock_for "$i"
   log "iface=$i assign_missing_restart_failed"
   continue
  fi
  release_lock_for "$i"
 done
 /etc/init.d/odhcpd restart
 refresh_pd_info
 cleanup_lan_nonmatching
 prune_excess_lan_gua
 recalc_lan_counts
fi
if [ "$RESTART_ALL" -ne 1 ]; then
 if [ "$LAN64_COUNT" -eq 0 ] && [ -n "$PD_LIST" ]; then
  log "lan_no_gua_bounce"
  cr=$(can_restart_iface "lan")
  if [ "$cr" = "1" ]; then
   update_cooldown "lan"
   ifdown lan
   sleep 1
   ifup lan
   /etc/init.d/odhcpd restart
   refresh_pd_info
   cleanup_lan_nonmatching
   prune_excess_lan_gua
   recalc_lan_counts
  else
   log "lan_bounce_cooldown_skip"
  fi
 fi
 if [ "$ASSIGNED64_COUNT" -eq 0 ] && [ -n "$PD_LIST" ] && [ "$LAN64_COUNT" -eq 0 ]; then
  log "lan_assign_missing_bounce"
  cr=$(can_restart_iface "lan")
  if [ "$cr" = "1" ]; then
   update_cooldown "lan"
   ifdown lan
   sleep 1
   ifup lan
   /etc/init.d/odhcpd restart
   refresh_pd_info
   cleanup_lan_nonmatching
   prune_excess_lan_gua
   recalc_lan_counts
  else
   log "lan_bounce_cooldown_skip"
  fi
 fi
 cleanup_lan_nonmatching
 prune_excess_lan_gua
 recalc_lan_counts
fi
if [ "$RESTART_ALL" -eq 1 ]; then
 RTARGETS=""
 for pg in $PD_GROUPS; do i=${pg#*@}; RTARGETS="$RTARGETS $i"; done
 for i in $DIALS; do
  found=0
  for t in $RTARGETS; do [ "$t" = "$i" ] && found=1 && break; done
  [ "$found" -eq 0 ] && RTARGETS="$RTARGETS $i"
 done
 RTARGETS=$(uniq_list "$RTARGETS")
 log "restart begin dial_ifaces=$DIALS targets=$RTARGETS"
 for i in $RTARGETS; do
  if ! acquire_lock_for "$i"; then
   log "iface=$i instance_running"
   continue
  fi
  if ! restart_and_check_iface "$i"; then
   release_lock_for "$i"
   log "iface=$i pd_missing_final_stop_sequence"
   break
  fi
  release_lock_for "$i"
 done
 /etc/init.d/odhcpd restart
 refresh_pd_info
  good=0
  t=0
  while [ $t -lt 20 ]; do
   t=$((t+1))
   sleep 2
   LIST=$(ip -6 addr show dev "$LAN_DEV" | awk '/inet6 /{print $2}')
   for ent in $LIST; do
    addr=${ent%/*}; mask=${ent#*/}
    [ -z "$addr" ] && continue
    [ "$mask" != "64" ] && continue
    case "$addr" in fe80:*|fd*|fc*) continue;; esac
    mm=$(match_any "$addr")
    if [ "$mm" = "1" ]; then good=1; break; fi
   done
   [ "$good" = "1" ] && break
  done
  if [ "$good" != "1" ]; then
   log "lan_gua_missing_bounce"
   cr=$(can_restart_iface "lan")
   if [ "$cr" = "1" ]; then
    update_cooldown "lan"
    ifdown lan
    sleep 1
    ifup lan
    /etc/init.d/odhcpd restart
    refresh_pd_info
    t=0
    good=0
    while [ $t -lt 20 ]; do
     t=$((t+1))
     sleep 2
     LIST=$(ip -6 addr show dev "$LAN_DEV" | awk '/inet6 /{print $2}')
     for ent in $LIST; do
      addr=${ent%/*}; mask=${ent#*/}
      [ -z "$addr" ] && continue
      [ "$mask" != "64" ] && continue
      case "$addr" in fe80:*|fd*|fc*) continue;; esac
      mm=$(match_any "$addr")
      if [ "$mm" = "1" ]; then good=1; break; fi
     done
     [ "$good" = "1" ] && break
    done
    if [ "$good" = "1" ]; then
     refresh_pd_info
     log "lan_gua_present_after_bounce"
     LIST=$(ip -6 addr show dev "$LAN_DEV" | awk '/inet6 /{print $2}')
     for ent in $LIST; do
      addr=${ent%/*}; mask=${ent#*/}
      [ -z "$addr" ] && continue
      [ "$mask" != "64" ] && continue
      case "$addr" in fe80:*|fd*|fc*) continue;; esac
      mm=$(match_any "$addr")
      if [ "$mm" != "1" ]; then
       ip -6 addr del "$addr/$mask" dev "$LAN_DEV" 2>/dev/null
       log "lan_addr_removed $addr/$mask"
      fi
     done
    else
     log "lan_gua_missing_final"
    fi
   else
    log "lan_bounce_cooldown_skip"
   fi
  else
   refresh_pd_info
   log "lan_gua_present"
   LIST=$(ip -6 addr show dev "$LAN_DEV" | awk '/inet6 /{print $2}')
   for ent in $LIST; do
    addr=${ent%/*}; mask=${ent#*/}
    [ -z "$addr" ] && continue
    [ "$mask" != "64" ] && continue
    case "$addr" in fe80:*|fd*|fc*) continue;; esac
    mm=$(match_any "$addr")
    if [ "$mm" != "1" ]; then
     ip -6 addr del "$addr/$mask" dev "$LAN_DEV" 2>/dev/null
     log "lan_addr_removed $addr/$mask"
    fi
   done
  fi
 log "restart done targets=$RTARGETS"
fi
cleanup_lan_nonmatching
recalc_lan_counts
mkdir -p "$(dirname "$STATE_FILE")" 2>/dev/null
echo "$LAN64_COUNT $ASSIGNED64_COUNT $MISMATCH $PD_LIST" > "$STATE_FILE"
log "state_updated"
forward=$(sysctl -n net.ipv6.conf.all.forwarding 2>/dev/null)
[ "$forward" != "1" ] && sysctl -w net.ipv6.conf.all.forwarding=1 >/dev/null 2>&1 && log "ipv6_forwarding_enabled"
rad=$(uci -q get odhcpd.@odhcpd[0].ra_default 2>/dev/null)
[ "$rad" != "1" ] && uci -q set odhcpd.@odhcpd[0].ra_default='1' && uci commit odhcpd && /etc/init.d/odhcpd restart && log "odhcpd_ra_default_enabled"
dr=$(ip -6 route show default | wc -l)
if [ "$dr" -lt 1 ]; then
 log "ipv6_default_route_missing"
 for i in $DIALS; do
  if ! acquire_lock_for "$i"; then
   log "iface=$i instance_running"
   continue
  fi
  log "iface=$i force_restart_due_to_missing_default"
  attempt=1
  max_attempt=2
  while [ $attempt -le $max_attempt ]; do
   ifdown "$i"
   sleep 1
   ifup "$i"
   t=0
   present=0
   while [ $t -lt 10 ]; do
    t=$((t+1))
    sleep 2
    pm=$(iface_pd_present "$i")
    if [ "$pm" = "1" ]; then present=1; break; fi
   done
   [ "$present" = "1" ] && break
   attempt=$((attempt+1))
  done
  release_lock_for "$i"
 done
 /etc/init.d/odhcpd restart
 refresh_pd_info
 cleanup_lan_nonmatching
 prune_excess_lan_gua
 recalc_lan_counts
fi

2 个赞