スポンサーリンク

OpenWrtを使用したv6プラスと固定IP

network

旧回線から新しく光クロス回線を引きました。enひかりです。光クロスの環境では、PPPoEを提供しているプロバイダーは極端に少なく、IPv6 IPoEでのインターネット接続しか選択肢がありません。今使っているプロバイダーでは、v6プラス(MAP-E)、Xpass(DS-Lite)が選択式になっていて、以前にDS−Liteを試用したので、今回はMAP-Eにしました。それと、今回は固定IP(IPIP6)も使えるようにしました。というわけで、環境が出来たので、OpenWrtで接続をした記録です。

OpenWrtの設定

wan6の設定

25.12ではONU直結からDHCPv6でアドレスを取得する場合、クライアントID設定が必要になりました。
設定しないとONUが応答しないので、IPv6のアドレスが降ってきません。

設定方法は次の通りです。

OpenWrt DHCPv6でIPv6アドレスが取得できないのでクライアントIDを設定する
最近の更新で追加されたDefault DUIDという設定が原因で、ONUからDHCPv6での取得ができなくなっていました。Snapshotでのお話なのでこのへんの動作まだ変わると思う。25.12.0-rc1で入ってるので、これから罠るとおも...

wan6にIPv6アドレスを割り当てるには、詳細設定の下の方にある、「IPv6割り当て長」を64にしますが、PDを受けて配布するルーターとして使うだけならここの設定は不要みたいです。

IPv6で設定していく

v6プラス、固定IPサービスのどちらもトンネルでIPv4通信をします。

wanにケーブル繋いでIPv6のアドレスを取得したら、必要なパッケージを追加していきます。
必要パッケージは map ip-full luci-proto-ipv6
素のOpenWrtではMAP-Eが使えないので次のソフトウェアインストールします。インストールはluciからやるか、コンソールでopkg installかapk add。

#snapshotだとapk
apk add map ip-full luci-proto-ipv6

luciからだと一つずつインストールで。めんどい

MAP-Eの設定

必須パッケージを入れて再起動をしたら、例のページからMAP-Eの設定情報を計算します。本来は設定サーバーから取ってくるらしいですが、認証が必要らしいので、手動計算します。

例のページ https://ip4.web.fc2.com/map-e.html

そのままoption出ているので、sshからconfig貼り付けるか、Webから入力します。
今回はwanをそのまま編集して設定変えました。

luciからだと [従来のMAPを使用] のチェックいれるのを忘れずに

全部設定が完了すると、トンネル用の_がついたインターフェース増えてIPv4接続されます。

なんか面白い資料見つけたので今度追記か実験します。読みたい人向けにこんな資料も 徹底解説 v6プラス

ページが見られなくなる問題

そのままではポートセットが一部しか使われず、ポートが枯渇してIPv4のページが開けなくなる問題が頻発するので、次のスクリプトをfirewall.userあたりに追加して実行されるようにします。が、めんどくさいのでスタートアップのローカルにべた貼りとかでもいいみたいです。

#!/bin/sh
# suggest https://aose.hatenablog.jp/entry/2022/12/04/224418
# this script based https://zenn.dev/wistnki
# use map version
# 

IPv4=[IP Address]
TUNDEV=map-wan
PSID=36
PREFIX=4096
BLOCKS=15

nft table ip mape_nat
nft delete table ip mape_nat

nft add table ip mape_nat
nft add map ip mape_nat chain_map { type mark : verdict \; }
nft add chain ip mape_nat POSTROUTING { type nat hook postrouting priority 100 \; }
nft add rule mape_nat POSTROUTING oifname $TUNDEV meta l4proto { tcp, udp, icmp } mark set numgen inc mod $BLOCKS offset 0x11 counter
nft add rule mape_nat POSTROUTING oifname $TUNDEV meta mark vmap @chain_map;

for r in `seq 1 $BLOCKS ` ; do
    mark=$(( r + 0x10 ))
    port_l=$(( r * PREFIX + PSID * 16 ))
    port_r=$((port_l + 15))
    
    nft add chain ip mape_nat mape_ports$r
    nft add rule ip mape_nat mape_ports$r meta l4proto { tcp, udp, icmp } counter snat to $IPv4:$port_l-$port_r persistent
    nft add element ip mape_nat chain_map { $mark : goto mape_ports$r }
done

他の参考も置いておく

https://pastebin.pl/view/raw/06212cd8
openWRTでV6プラス/BIGLOBEに接続するためのスクリプト
openWRTでV6プラス/BIGLOBEに接続するためのスクリプト. GitHub Gist: instantly share code, notes, and snippets.

俗にニチバンベンチとか言われていましたが、IPv4でしか接続出来ないサイトに接続すると、ポート不足でページ開けなくなる問題を軽減してくれます。それでもF5しすぎると駄目なところは駄目っぽいので、固定IP使うしかなさそう。

固定IPサービス

固定IPサービスはipip6での単純トンネル。PPPoE接続の感覚で利用できます。
予算があったらぜひ使いたい固定IP。
なお、NTTのレンタルルーターなど業務機でしか対応機種が無いみたいで、一般のWiFiルーターは対応状況壊滅。

あとはOpenWrtなどソフトルーターが選択肢。

IPIP6の設定

難しいことはなく、プロバイダから届いた設定のとおりに入力していきますが、
たぶんプロバイダはPrefixを書いてこないので、察して降ってきたwan6のアドレスで入力していきます。

インターフェイスを新規作成でwan2を作って設定します

リモートIPv6アドレスにBRアドレス
IPv4アドレスは、割当られた固定IP
IPv6アドレスは、今降ってきているPrefix + プロバイダが通知してきたIPv6インターフェイスID(後半のアドレス)
MTUは1460
注意点として、hintに必ず0を入力します。
ファイアウォール設定はとりあえずWANと同じにしておきます。

アップデートURLも通知送られてきていると思いますが、IPv6の割当変更が起きたときに叩くみたいです。今回は特に何もしなくてもつながっているので未使用です。

23.05以前のOpenWrtの場合はパッケージ壊れているとか、そもそもIPIP6非対応なので、githubからスクリプト取ってきて置き換え必要です。

というわけでv6プラスに固定IPサービスしたら、実質2つIPが使えるようになりました。

ちょっと思っていたのと違うんですけど、mwan3で使い分けたりですかね。

最近の所感、IPv6でほとんどのサイトが繋がるようになってきていて、IPv4の必須性は薄れつつあるように思います。現状困るようにおもえるのは、githubとYahooくらいです。

HGWを契約していると/56でしかMAP-E張れないらしく、電話で設定を/60変更してもらう必要があるそうで注意が必要です。

Amazon | Linksys Velop WRT Pro 7 ホーム&ビジネス向け OpenWrt ベース QSDK搭載 WiFi7 トライバンドルーター LN6001-JP NT1516 | Linksys | 無線・有線LANルーター 通販
Linksys Velop WRT Pro 7 ホーム&ビジネス向け OpenWrt ベース QSDK搭載 WiFi7 トライバンドルーター LN6001-JP NT1516が無線・有線LANルーターストアでいつでもお買い得。当日お急ぎ便対...

mapcalc

もしかしたら一部の環境の人には使えるかもしれないので置いときます。
問題が起きないのでテストしかしていないですが、 /usr/sbin/mapcalc を置き換えて chmod +x で権限あげてください

OpenWRT mapcalc Issues on 64-bit Architecture / OpenWRTの64bitアーキテクチャで、mapcalcが適切に動かない話 [MAP-E]

#!/bin/sh
# mapcalc - MAP parameter calculation (pure shell implementation)
#
# Replaces the C binary.  Avoids the 64-bit undefined behaviour in the
# original port-set computation (negative shift amount in
# `1 << (16 - offset - psidlen)`) by performing all integer arithmetic
# in the shell, which uses 64-bit signed integers on 64-bit targets.
#
# Derived from mapcalc.c:
#   Author:    Steven Barth <[email protected]>
#   Copyright: (c) 2014-2015 cisco Systems, Inc.
#              (c) 2015 Steven Barth <[email protected]>
#              (c) 2018 Hans Dedecker <[email protected]>
# Shell port: 
#
# SPDX-License-Identifier: GPL-2.0-only

LEGACY="${LEGACY:-0}"

[ $# -lt 2 ] && {
	echo "Usage: $0 <interface|*> <rule1> [rule2] [...]" >&2
	exit 1
}

MAIN_IFACE="$1"
shift

# ---------------------------------------------------------------------------
# IPv6 / IPv4 utilities
# IPv6 addresses are represented internally as 32-char lowercase hex strings,
# MSB first, no separators.  e.g. "20010db8000000000000000000000001"
# ---------------------------------------------------------------------------

# Expand any valid IPv6 address to 32 lowercase hex chars.
# Uses awk with a pure-decimal hex converter (portable to busybox awk).
ipv6_to_hex() {
	echo "$1" | awk '
	function h2d(h,   i,r,c,n,a) {
		r = 0; n = split(toupper(h), a, "")
		for (i = 1; i <= n; i++) {
			c = index("0123456789ABCDEF", a[i]) - 1
			r = r * 16 + c
		}
		return r
	}
	{
		addr = $0
		if (index(addr, "::") > 0) {
			split(addr, h, "::")
			nl = split(h[1], L, ":")
			nr = split(h[2], R, ":")
			miss = 8 - nl - nr; j = 0
			for (i = 1; i <= nl;   i++) g[++j] = L[i]
			for (i = 1; i <= miss; i++) g[++j] = "0"
			for (i = 1; i <= nr;   i++) g[++j] = R[i]
		} else {
			split(addr, g, ":")
		}
		out = ""
		for (i = 1; i <= 8; i++) out = out sprintf("%04x", h2d(g[i]))
		print out
	}'
}

# 32-char hex → IPv6 string (not compressed, but valid for shell variable output).
hex_to_ipv6() {
	local h="$1"
	printf '%x:%x:%x:%x:%x:%x:%x:%x' \
		$(( 0x${h:0:4} ))  $(( 0x${h:4:4} )) \
		$(( 0x${h:8:4} ))  $(( 0x${h:12:4} )) \
		$(( 0x${h:16:4} )) $(( 0x${h:20:4} )) \
		$(( 0x${h:24:4} )) $(( 0x${h:28:4} ))
}

# Dotted-decimal IPv4 → 8-char hex.
ipv4_to_hex() {
	local IFS=.
	# shellcheck disable=SC2086
	set -- $1
	printf '%02x%02x%02x%02x' "$1" "$2" "$3" "$4"
}

# 8-char hex → dotted-decimal IPv4.
hex_to_ipv4() {
	local h="$1"
	printf '%d.%d.%d.%d' \
		$(( 0x${h:0:2} )) $(( 0x${h:2:2} )) \
		$(( 0x${h:4:2} )) $(( 0x${h:6:2} ))
}

# Return 0 (true) if the first BITS bits of hex strings H1 and H2 match.
# Mirrors: bmemcmp(h1, h2, bits) == 0
ipv6_prefix_match() {
	local h1="$1" h2="$2" bits="$3"
	local full=$(( bits / 4 )) rem=$(( bits % 4 ))
	[ "${h1:0:$full}" = "${h2:0:$full}" ] || return 1
	if [ "$rem" -gt 0 ]; then
		# Mask keeping the top REM bits of a nibble:
		#   rem=1→8, rem=2→12, rem=3→14
		local mask=$(( ~((1 << (4 - rem)) - 1) & 0xf ))
		[ $(( 0x${h1:$full:1} & mask )) -eq \
		  $(( 0x${h2:$full:1} & mask )) ] || return 1
	fi
	return 0
}

# Extract NBITS bits starting at bit FROM (MSB = bit 0) from a 32-char hex
# IPv6 string.  Returns the value right-aligned as a decimal integer.
# Mirrors: bmemcpys64 + right-alignment of the result.
# Requires NBITS <= 56 (fits in 64-bit shell arithmetic).
ipv6_extract_bits() {
	local hex="$1" from="$2" nbits="$3"
	[ "$nbits" -eq 0 ] && { echo 0; return; }
	local nib_start=$(( from / 4 ))
	local nib_count=$(( (from + nbits - 1) / 4 - nib_start + 1 ))
	local chunk="${hex:$nib_start:$nib_count}"
	# printf '%d' "0x..." handles hex correctly in bash and busybox.
	local val; val=$(printf '%d' "0x$chunk")
	local rsh=$(( nib_count * 4 - (from % 4) - nbits ))
	echo $(( (val >> rsh) & ((1 << nbits) - 1) ))
}

# Overlay NBITS bits at position FROM (MSB = bit 0) in a 32-char hex string
# with integer VAL (right-aligned).  Returns the modified 32-char hex string.
# Mirrors: bmemcpy(dst, src_as_int, nbits) at a given bit offset.
ipv6_set_bits() {
	local hex="$1" from="$2" nbits="$3" val="$4"
	[ "$nbits" -eq 0 ] && { echo "$hex"; return; }
	local nib_start=$(( from / 4 ))
	local nib_count=$(( (from + nbits - 1) / 4 - nib_start + 1 ))
	local bit_offset=$(( from % 4 ))
	local rsh=$(( nib_count * 4 - bit_offset - nbits ))
	local nbits_total=$(( nib_count * 4 ))
	local cur; cur=$(printf '%d' "0x${hex:$nib_start:$nib_count}")
	local width_mask=$(( ~(~0 << nbits_total) ))
	local placed=$(( (val << rsh) & width_mask ))
	local cleared=$(( cur & ~(((1 << nbits) - 1) << rsh) & width_mask ))
	local new_hex; new_hex=$(printf "%0${nib_count}x" $(( cleared | placed )))
	echo "${hex:0:$nib_start}${new_hex}${hex:$((nib_start + nib_count))}"
}

# ---------------------------------------------------------------------------
# ubus / PD discovery
# Mirrors handle_dump() + match_prefix() + the PD-search loop.
# ---------------------------------------------------------------------------

_DUMP=""
_DUMP_LOADED=0

_load_dump() {
	[ "$_DUMP_LOADED" -eq 0 ] || return 0
	_DUMP=$(ubus call network.interface dump 2>/dev/null)
	_DUMP_LOADED=1
}

# Search ubus for the longest IPv6 prefix delegation matching the rule.
# On success sets: RESULT_PDLEN, RESULT_PD_HEX, RESULT_IFACE.
find_pd() {
	local want_iface="$1" ipv6prefix_hex="$2" prefix6len="$3" lw4o6="$4"

	RESULT_PDLEN=-1
	RESULT_PD_HEX="00000000000000000000000000000000"
	RESULT_IFACE=""

	_load_dump
	[ -n "$_DUMP" ] || return 1

	local idx=0
	while true; do
		local ifname
		ifname=$(echo "$_DUMP" | \
			jsonfilter -e "$.interface[$idx].interface" 2>/dev/null)
		[ -n "$ifname" ] || break

		if [ "$want_iface" = "*" ] || [ "$want_iface" = "$ifname" ]; then
			_match_array "$idx" "ipv6-prefix" \
				"$ipv6prefix_hex" "$prefix6len" "$ifname" 0
			[ "$lw4o6" -eq 1 ] && \
				_match_array "$idx" "ipv6-address" \
					"$ipv6prefix_hex" "$prefix6len" "$ifname" 1
		fi

		idx=$(( idx + 1 ))
	done
}

# Iterate one ipv6-prefix or ipv6-address array for interface index IDX.
_match_array() {
	local iidx="$1" key="$2" ipv6prefix_hex="$3" \
	      prefix6len="$4" ifname="$5" is_addr="$6"
	local pidx=0
	while true; do
		local paddr pmask
		paddr=$(echo "$_DUMP" | \
			jsonfilter -e "$.interface[$iidx][\"$key\"][$pidx].address" \
			2>/dev/null)
		[ -n "$paddr" ] || break
		pmask=$(echo "$_DUMP" | \
			jsonfilter -e "$.interface[$iidx][\"$key\"][$pidx].mask" \
			2>/dev/null)

		# lw4o6: treat /128 unicast address as /64 PD (C line 138-139)
		[ "$is_addr" -eq 1 ] && [ "$pmask" -eq 128 ] && pmask=64

		local phex; phex=$(ipv6_to_hex "$paddr")

		if [ "$pmask" -ge "$prefix6len" ] && \
		   ipv6_prefix_match "$phex" "$ipv6prefix_hex" "$prefix6len"; then
			# Longest-prefix wins
			if [ "$pmask" -gt "$RESULT_PDLEN" ]; then
				RESULT_PDLEN=$pmask
				RESULT_PD_HEX=$phex
				RESULT_IFACE=$ifname
			fi
		elif [ "$is_addr" -eq 1 ] && \
		     [ "$RESULT_PDLEN" -lt "$prefix6len" ] && \
		     [ "$pmask" -lt "$prefix6len" ] && \
		     ipv6_prefix_match "$phex" "$ipv6prefix_hex" "$pmask"; then
			# lw4o6 fallback: use rule prefix as PD (C lines 145-148)
			local pd_hex="00000000000000000000000000000000"
			local pfx_val; pfx_val=$(ipv6_extract_bits \
				"$ipv6prefix_hex" 0 "$prefix6len")
			pd_hex=$(ipv6_set_bits "$pd_hex" 0 "$prefix6len" "$pfx_val")
			RESULT_PDLEN=$prefix6len
			RESULT_PD_HEX=$pd_hex
			RESULT_IFACE=$ifname
		fi

		pidx=$(( pidx + 1 ))
	done
}

# ---------------------------------------------------------------------------
# Main rule-processing loop
# ---------------------------------------------------------------------------

rulecnt=0
status=0

for rule_arg in "$@"; do
	# --- Parse comma-separated key=value options (mirrors getsubopt loop) ---
	lw4o6=0 fmr=0 ealen=-1 addr4len=32
	prefix4len=32 prefix6len=-1 pdlen=-1
	ipv4prefix_hex="00000000"
	ipv6prefix_hex="00000000000000000000000000000000"
	pd_hex="00000000000000000000000000000000"
	offset=-1 psidlen=-1 psid=-1
	dmr="" br=""
	cur_iface="$MAIN_IFACE"

	rule="${rule_arg},"
	while [ -n "$rule" ]; do
		token="${rule%%,*}"; rule="${rule#*,}"
		[ -n "$token" ] || continue
		key="${token%%=*}"; val="${token#*=}"
		[ "$key" = "$token" ] && val=""

		case "$key" in
		type)      [ "$val" = "lw4o6" ] && lw4o6=1 ;;
		fmr)       fmr=1 ;;
		ealen)     expr "$val" : '^[0-9]*$' >/dev/null 2>&1 && \
		           [ "$val" -le 48 ] && ealen=$val ;;
		prefix4len) expr "$val" : '^[0-9]*$' >/dev/null 2>&1 && \
		           [ "$val" -le 32 ] && prefix4len=$val ;;
		prefix6len) expr "$val" : '^[0-9]*$' >/dev/null 2>&1 && \
		           [ "$val" -le 128 ] && prefix6len=$val ;;
		ipv4prefix) ipv4prefix_hex=$(ipv4_to_hex "$val") ;;
		ipv6prefix) ipv6prefix_hex=$(ipv6_to_hex "$val") ;;
		pd)        pd_hex=$(ipv6_to_hex "$val") ;;
		offset)    expr "$val" : '^[0-9]*$' >/dev/null 2>&1 && \
		           [ "$val" -le 16 ] && offset=$val ;;
		psidlen)   expr "$val" : '^[0-9]*$' >/dev/null 2>&1 && \
		           [ "$val" -le 16 ] && psidlen=$val ;;
		pdlen)     expr "$val" : '^[0-9]*$' >/dev/null 2>&1 && \
		           [ "$val" -le 128 ] && pdlen=$val ;;
		psid)      expr "$val" : '^[0-9]*$' >/dev/null 2>&1 && \
		           [ "$val" -le 65535 ] && psid=$val ;;
		dmr)       dmr="$val" ;;
		br)        br="$val" ;;
		*)         [ -n "$key" ] && \
		           echo "Skipped invalid option: $key" >&2 ;;
		esac
	done

	# --- Defaults (mirrors C lines 275-283) ---
	if [ "$offset" -lt 0 ]; then
		if   [ "$lw4o6"  -eq 1 ]; then offset=0
		elif [ "$LEGACY" -eq 1 ]; then offset=4
		else                            offset=6
		fi
	fi
	if [ "$lw4o6" -eq 1 ]; then
		[ "$psidlen" -lt 0 ] && psidlen=0
		ealen=$psidlen
	fi

	# --- PD discovery from ubus (mirrors C lines 287-308) ---
	if [ "$pdlen" -lt 0 ]; then
		find_pd "$MAIN_IFACE" "$ipv6prefix_hex" "$prefix6len" "$lw4o6"
		pdlen=$RESULT_PDLEN
		pd_hex=$RESULT_PD_HEX
		cur_iface=$RESULT_IFACE
	fi

	# --- Derive ealen (C line 310-311) ---
	[ "$ealen" -lt 0 ] && [ "$pdlen" -ge 0 ] && \
		ealen=$(( pdlen - prefix6len ))

	# --- Derive psidlen (C lines 313-318) ---
	if [ "$psidlen" -le 0 ]; then
		psidlen=$(( ealen - (32 - prefix4len) ))
		[ "$psidlen" -lt 0 ] && psidlen=0
		psid=-1
	fi

	# --- Validate (C line 321, extended: offset+psidlen <= 16 per RFC 7597) ---
	if [ "$prefix4len" -lt 0 ] || [ "$prefix6len" -lt 0 ] || \
	   [ "$ealen" -lt 0 ] || [ "$psidlen" -gt 16 ] || \
	   [ "$ealen" -lt "$psidlen" ] || \
	   [ $(( offset + psidlen )) -gt 16 ]; then
		echo "Skipping invalid or incomplete rule: $rule_arg" >&2
		status=1; continue
	fi

	# --- Extract PSID from PD (C lines 327-329) ---
	if [ "$psid" -lt 0 ] && [ "$psidlen" -ge 0 ] && [ "$pdlen" -ge 0 ]; then
		psid=$(ipv6_extract_bits "$pd_hex" \
			$(( prefix6len + ealen - psidlen )) "$psidlen")
	fi

	# --- Normalise PSID (C lines 332-336) ---
	# In C, psid arrives left-aligned (from bmemcpys64/be16_to_cpu) so it needs
	# >> (16-psidlen) to get the right-aligned value.  In shell, ipv6_extract_bits
	# already returns a right-aligned integer, so no right-shift is needed.
	#   psid_value: right-aligned PSID integer (fits in psidlen bits)
	#   psid:       psid_value << (16 - psidlen), left-aligned for port computation
	psid_value=0; psid16_hex="0000"
	if [ "$psidlen" -gt 0 ]; then
		psid_value=$psid
		psid16_hex=$(printf '%04x' "$psid_value")
		psid=$(( psid_value << (16 - psidlen) ))
	fi

	# --- IPv4 address (C lines 338-345) ---
	# bmemcpys64 extracts ea_bits bits from PD[prefix6len:] left-aligned in 32b,
	# then right-shifts by prefix4len, then overlays the top prefix4len bits
	# with the IPv4 prefix.
	ipv4addr_hex="00000000"; addr4len=32
	ea_bits=$(( ealen - psidlen ))

	if [ "$pdlen" -ge 0 ] || [ "$ealen" -eq "$psidlen" ]; then
		if [ "$ea_bits" -gt 0 ]; then
			raw=$(ipv6_extract_bits "$pd_hex" "$prefix6len" "$ea_bits")
			ea_shift=$(( 32 - prefix4len - ea_bits ))
			ea_placed=$(( raw << ea_shift ))
		else
			ea_placed=0
		fi
		host_bits=$(( 32 - prefix4len ))
		if [ "$host_bits" -ge 32 ]; then
			prefix_mask=0
		else
			prefix_mask=$(( ~((1 << host_bits) - 1) & 0xffffffff ))
		fi
		ipv4addr_hex=$(printf '%08x' \
			$(( (0x$ipv4prefix_hex & prefix_mask) | ea_placed )))

		[ $(( prefix4len + ealen )) -lt 32 ] && \
			addr4len=$(( prefix4len + ealen ))
	fi

	# --- Non-FMR without PD: skip (C lines 347-350) ---
	if [ "$pdlen" -lt 0 ] && [ "$fmr" -eq 0 ]; then
		echo "Skipping non-FMR without matching PD: $rule_arg" >&2
		status=1; continue
	fi

	# --- CE IPv6 address (C lines 351-356) ---
	# Start with zeros; embed IPv4+PSID at byte 10 (or 9 if LEGACY);
	# then overlay the first pdlen bits with PD.
	ipv6addr_hex="00000000000000000000000000000000"
	if [ "$pdlen" -ge 0 ]; then
		[ "$LEGACY" -eq 1 ] && v4nib=18 || v4nib=20  # byte→nibble offset

		# Set IPv4 (8 nibbles) and PSID (4 nibbles) at the right position
		ipv6addr_hex="${ipv6addr_hex:0:$v4nib}${ipv4addr_hex}${psid16_hex}${ipv6addr_hex:$((v4nib+12))}"

		# Overlay first pdlen bits with PD (bmemcpy)
		full_nibs=$(( pdlen / 4 ))
		rem_bits=$(( pdlen % 4 ))
		if [ "$rem_bits" -eq 0 ]; then
			ipv6addr_hex="${pd_hex:0:$full_nibs}${ipv6addr_hex:$full_nibs}"
		else
			mask=$(( ~((1 << (4 - rem_bits)) - 1) & 0xf ))
			pd_nib=$(( 0x${pd_hex:$full_nibs:1} & mask ))
			ad_nib=$(( 0x${ipv6addr_hex:$full_nibs:1} & (~mask & 0xf) ))
			mg=$(printf '%x' $(( pd_nib | ad_nib )))
			ipv6addr_hex="${pd_hex:0:$full_nibs}${mg}${ipv6addr_hex:$((full_nibs+1))}"
		fi
	fi

	# --- Output ---
	rulecnt=$(( rulecnt + 1 ))

	printf 'RULE_%d_FMR=%d\n'        "$rulecnt" "$fmr"
	printf 'RULE_%d_EALEN=%d\n'      "$rulecnt" "$ealen"
	printf 'RULE_%d_PSIDLEN=%d\n'    "$rulecnt" "$psidlen"
	printf 'RULE_%d_OFFSET=%d\n'     "$rulecnt" "$offset"
	printf 'RULE_%d_PREFIX4LEN=%d\n' "$rulecnt" "$prefix4len"
	printf 'RULE_%d_PREFIX6LEN=%d\n' "$rulecnt" "$prefix6len"
	printf 'RULE_%d_IPV4PREFIX=%s\n' "$rulecnt" "$(hex_to_ipv4 "$ipv4prefix_hex")"
	printf 'RULE_%d_IPV6PREFIX=%s\n' "$rulecnt" "$(hex_to_ipv6 "$ipv6prefix_hex")"

	if [ "$pdlen" -ge 0 ]; then
		printf 'RULE_%d_IPV6PD=%s\n'   "$rulecnt" "$(hex_to_ipv6 "$pd_hex")"
		printf 'RULE_%d_PD6LEN=%d\n'   "$rulecnt" "$pdlen"
		printf 'RULE_%d_PD6IFACE=%s\n' "$rulecnt" "$cur_iface"
		printf 'RULE_%d_IPV6ADDR=%s\n' "$rulecnt" "$(hex_to_ipv6 "$ipv6addr_hex")"
		printf 'RULE_BMR=%d\n'         "$rulecnt"
	fi

	[ "$ipv4addr_hex" != "00000000" ] && {
		printf 'RULE_%d_IPV4ADDR=%s\n' "$rulecnt" "$(hex_to_ipv4 "$ipv4addr_hex")"
		printf 'RULE_%d_ADDR4LEN=%d\n' "$rulecnt" "$addr4len"
	}

	# --- Port sets (mirrors C lines 394-407) ---
	# All inputs have been validated: offset + psidlen <= 16, so
	# suffix_bits >= 0 — the undefined behaviour from the C code cannot occur.
	if [ "$psidlen" -gt 0 ] && [ "$psid" -ge 0 ]; then
		printf "RULE_%d_PORTSETS='" "$rulecnt"
		suffix_bits=$(( 16 - offset - psidlen ))
		k=$(( offset ? 1 : 0 ))
		k_max=$(( 1 << offset ))
		while [ "$k" -lt "$k_max" ]; do
			start=$(( (k << (16 - offset)) | (psid >> offset) ))
			end=$(( start + (1 << suffix_bits) - 1 ))
			[ "$start" -eq 0 ] && start=1
			[ "$start" -le "$end" ] && printf '%d-%d ' "$start" "$end"
			k=$(( k + 1 ))
		done
		printf "'\n"
	fi

	[ -n "$dmr" ] && printf 'RULE_%d_DMR=%s\n' "$rulecnt" "$dmr"
	[ -n "$br"  ] && printf 'RULE_%d_BR=%s\n'  "$rulecnt" "$br"
done

printf 'RULE_COUNT=%d\n' "$rulecnt"
exit $status

コメント

タイトルとURLをコピーしました