我想根据给定的 IPv6 地址更新 djbdns (dbndns) 配置文件,例如
2a01:488:66:1000:523:f116:0:1
或 ::1
。
dbndns 需要扩展的 IPv6 地址,例如
2a010488006610000523f11600000001
为 2a01:488:66:1000:523:f116:0:1
。
扩展此类 IPv6 地址的最简单方法是什么?
使用
sipcalc
可能会做到这一点。它提供的信息比您需要的更多,但是一点 grep
和 cut
可以解决这个问题:-)
$ EXPANDED=`sipcalc 2001::1 | fgrep Expanded | cut -d '-' -f 2`
$ echo $EXPAND
2001:0000:0000:0000:0000:0000:0000:0001
作为参考,这是
sipcalc
的完整输出:
$ sipcalc 2001::1
-[ipv6 : 2001::1] - 0
[IPV6 INFO]
Expanded Address - 2001:0000:0000:0000:0000:0000:0000:0001
Compressed address - 2001::1
Subnet prefix (masked) - 2001:0:0:0:0:0:0:1/128
Address ID (masked) - 0:0:0:0:0:0:0:0/128
Prefix address - ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
Prefix length - 128
Address type - Aggregatable Global Unicast Addresses
Network range - 2001:0000:0000:0000:0000:0000:0000:0001 -
2001:0000:0000:0000:0000:0000:0000:0001
我最近想要一个无依赖的解决方案,可以跨 shell 移植并在 openwrt 等平台上工作。我想出了以下片段:
# helper to convert hex to dec (portable version)
hex2dec(){
[ "$1" != "" ] && printf "%d" "$(( 0x$1 ))"
}
# expand an ipv6 address
expand_ipv6() {
ip=$1
# prepend 0 if we start with :
echo $ip | grep -qs "^:" && ip="0${ip}"
# expand ::
if echo $ip | grep -qs "::"; then
colons=$(echo $ip | sed 's/[^:]//g')
missing=$(echo ":::::::::" | sed "s/$colons//")
expanded=$(echo $missing | sed 's/:/:0/g')
ip=$(echo $ip | sed "s/::/$expanded/")
fi
blocks=$(echo $ip | grep -o "[0-9a-f]\+")
set $blocks
printf "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n" \
$(hex2dec $1) \
$(hex2dec $2) \
$(hex2dec $3) \
$(hex2dec $4) \
$(hex2dec $5) \
$(hex2dec $6) \
$(hex2dec $7) \
$(hex2dec $8)
}
我也有这个功能来压缩
# returns a compressed ipv6 address under the form recommended by RFC5952
compress_ipv6() {
ip=$1
blocks=$(echo $ip | grep -o "[0-9a-f]\+")
set $blocks
# compress leading zeros
ip=$(printf "%x:%x:%x:%x:%x:%x:%x:%x\n" \
$(hex2dec $1) \
$(hex2dec $2) \
$(hex2dec $3) \
$(hex2dec $4) \
$(hex2dec $5) \
$(hex2dec $6) \
$(hex2dec $7) \
$(hex2dec $8)
)
# prepend : for easier matching
ip=:$ip
# :: must compress the longest chain
for pattern in :0:0:0:0:0:0:0:0 \
:0:0:0:0:0:0:0 \
:0:0:0:0:0:0 \
:0:0:0:0:0 \
:0:0:0:0 \
:0:0; do
if echo $ip | grep -qs $pattern; then
ip=$(echo $ip | sed "s/$pattern/::/")
# if the substitution occured before the end, we have :::
ip=$(echo $ip | sed 's/:::/::/')
break # only one substitution
fi
done
# remove prepending : if necessary
echo $ip | grep -qs "^:[^:]" && ip=$(echo $ip | sed 's/://')
echo $ip
}
您可以将它们组合起来测试给定的输入是否是 ipv6
# a valid ipv6 is either the expanded form or the compressed one
is_ipv6(){
expanded="$(expand_ipv6 $1)"
[ "$1" = "$expanded" ] && return 0
compressed="$(compress_ipv6 $expanded)"
[ "$1" = "$compressed" ] && return 0
return 1
}
我希望这有帮助!这些片段取自https://github.com/chmduquesne/wg-ip。如果您发现任何错误,请贡献!
__rfc5952_expand () {
read addr mask < <(IFS=/; echo $1)
quads=$(grep -oE "[a-fA-F0-9]{1,4}" <<< ${addr/\/*} | wc -l)
#[ "${addr:${#addr}-1}" == ":" ] && { addr="${addr}0000"; (( quads++ )); }
grep -qs ":$" <<< $addr && { addr="${addr}0000"; (( quads++ )); }
grep -qs "^:" <<< $addr && { addr="0000${addr}"; (( quads++ )); }
[ $quads -lt 8 ] && addr=${addr/::/:$(for (( i=1; i<=$(( 8 - quads )) ; i++ )); do printf "0000:"; done)}
#addr=$(
#for quad in $(IFS=:; echo ${addr}); do
# [ "${#quad}" -lt 4 ] && for (( i=${#quad}; i<4 ; i++ )); do quad=0${quad}; done
# printf "${delim}${quad}"; delim=":";
# Or so if you need result without colon, as asked in first post
# printf "${quad}";
#done)
addr=$(for quad in $(IFS=:; echo ${addr}); do printf "${delim}%04x" "0x${quad}"; delim=":"; done)
#addr=$(for quad in $(IFS=:; echo ${addr}); do printf "%04x" "0x${quad}"; done)
[ ! -z $mask ] && echo $addr/$mask || echo $addr
}
for ip in 2a01:4f8:211:9e::/64 ::1/128; do __rfc5952_expand $ip; done
2a01:04f8:0211:009e:0000:0000:0000:0000/64
0000:0000:0000:0000:0000:0000:0000:0001/128
__rfc5952_compact () {
read addr mask < <(IFS=/; echo $1)
addr=$(for quad in $(IFS=:; echo ${addr}); do printf "${delim}%x" "0x${quad}"; delim=":"; done)
for zeros in $(grep -oE "((^|:)0)+:?" <<< $addr | sort -r | head -1); do addr=${addr/$zeros/::}; done
[ ! -z $mask ] && echo $addr/$mask || echo $addr
}
for ip in 2a01:04f8:0211:009e:00:0001:0000:0000/64 0000:0000:0000:0000:0000:0000:0000:0001/128; do __rfc5952_compact $ip; done
2a01:4f8:211:9e:0:1::/64
::1/128
使用 awk,你可以这样做:
$ echo 2001::1 | awk '{if(NF<8){inner = "0"; for(missing = (8 - NF);missing>0;--missing){inner = inner ":0"}; if($2 == ""){$2 = inner} else if($3 == ""){$3 = inner} else if($4 == ""){$4 = inner} else if($5 == ""){$5 = inner} else if($6 == ""){$6 = inner} else if($7 == ""){$7 = inner}}; print $0}' FS=":" OFS=":" | awk '{for(i=1;i<9;++i){len = length($(i)); if(len < 1){$(i) = "0000"} else if(len < 2){$(i) = "000" $(i)} else if(len < 3){$(i) = "00" $(i)} else if(len < 4){$(i) = "0" $(i)} }; print $0}' FS=":" OFS=":"
$ 2001:0000:0000:0000:0000:0000:0000:0001
第一次调用 awk 在“::”之间添加缺失的零和冒号;第二次调用 awk 将缺失的 0 添加到每个组中。
要修剪冒号,只需将最后一个 OFS=":" 替换为 OFS=""。
这适合你吗?
kent$ echo "2a01:488:66:1000:523:f116:0:1"|awk -F: '{for(i=1;i<=NF;i++)x=x""sprintf ("%4s", $i);gsub(/ /,"0",x);print x}'
2a010488006610000523f11600000001
POSIX shell 解决方案:
#!/bin/sh
expand_ipv6()
{
__expand_ipv6_ip="${1%%/*}"
__expand_ipv6_mask=""
# extract and filter mask at end of address
case "$1" in
*/*)
__expand_ipv6_mask="${1#*/}"
__expand_ipv6_mask="/${__expand_ipv6_mask%%[^0-9/]*}"
esac
case "$__expand_ipv6_ip" in
:*) __expand_ipv6_ip="0$__expand_ipv6_ip"
esac
case "$__expand_ipv6_ip" in
*::*)
__expand_ipv6_colons="$(echo "$__expand_ipv6_ip" | tr -c -d ':')"
__expand_ipv6_expanded="$(echo ":::::::::" | sed -e "s/$__expand_ipv6_colons//" -e 's/:/:0/g')"
__expand_ipv6_ip="$(echo "$__expand_ipv6_ip" | sed "s/::/$__expand_ipv6_expanded/")"
;;
esac
__expand_ipv6_blocks="$(echo "$__expand_ipv6_ip" | grep -o '[0-9a-f]\+' | while read -r __expand_ipv6_hex; do [ -n "$__expand_ipv6_hex" ] && printf " %d" "$((0x$__expand_ipv6_hex % 65536))"; done)"
printf "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x" $__expand_ipv6_blocks
printf "%s\n" "$__expand_ipv6_mask"
}
测试:
> expand_ipv6 ab:12:345f::2/32
00ab:0012:345f:0000:0000:0000:0000:0002/32
这是基于 user48768 的这个答案的稍微改进的版本,他从这个 github 源复制了它,其中:
__expand_ipv6_
-前缀)注意:
expand_ipv6
函数需要一个有效的IPv6地址,否则结果也不一定有效。要测试字符串是否是有效的 IPv6 地址,请使用:
#!/bin/sh
is_ipv6()
{
ip -6 route get "$1" >/dev/null 2>/dev/null || [ $? -ne 1 ]
}
此外,为了压缩 IPv6 地址(expand_ipv6 的相反),这里还有一个改进的解决方案:
#!/bin/sh
compress_ipv6()
{
__compress_ipv6_ip="$(echo "$1" | sed -e 's/::/:0:/g' | grep -o "[0-9a-f]\+" | while read -r __compress_ipv6_hex; do [ -n "$__compress_ipv6_hex" ] && printf ":%x" "$((0x$__compress_ipv6_hex))"; done)"
for __compress_ipv6_chain in :0:0:0:0:0:0:0:0 :0:0:0:0:0:0:0 :0:0:0:0:0:0 :0:0:0:0:0 :0:0:0:0 :0:0:0 :0:0 :0
do
case "$__compress_ipv6_ip" in
*$__compress_ipv6_chain*)
__compress_ipv6_ip="$(echo "$__compress_ipv6_ip" | sed -e "s/$__compress_ipv6_chain/::/" -e 's/:::/::/')"
break
esac
done
case "$__compress_ipv6_ip" in
::*) ;;
:*) __compress_ipv6_ip="${__compress_ipv6_ip#:}"
esac
echo "$__compress_ipv6_ip"
}
awk 详细版本:
{
OFS=":"
FS=":"
j=NF;
for(i=8;i>=1;i--) {
# If the i field is empty and there are still missing fields (j<i)
if (!$(i) && j<i) {
# if j has a value, copy to i and clean j
if($(j)) {
$(i)=$(j);
$(j)="";
j--
# if not, fill i it with zero
} else {
$(i)=0
}
}
# Now just add the leading 0
$(i)=gensub(" ","0","g",sprintf("%4s",$(i)))
}
print
}
还有紧凑型 oneliner:
awk -v OFS=":" -F: '{j=NF;for(i=8;i>=1;i--) {if (!$(i) && j<i) {if($(j)) {$(i)=$(j);$(j)="";j--} else $(i)=0} $(i)=gensub(" ","0","g",sprintf("%4s",$(i))) } print}'
您可以使用它从输入中读取:
$ echo ::1 | awk -v OFS=":" -F: '{j=NF;for(i=8;i>=1;i--) {if (!$(i) && j<i) {if($(j)) {$(i)=$(j);$(j)="";j--} else $(i)=0} $(i)=gensub(" ","0","g",sprintf("%4s",$(i))) } print}'
0000:0000:0000:0000:0000:0000:0000:0001
$ echo 2001::1 | awk -v OFS=":" -F: '{j=NF;for(i=8;i>=1;i--) {if (!$(i) && j<i) {if($(j)) {$(i)=$(j);$(j)="";j--} else $(i)=0} $(i)=gensub(" ","0","g",sprintf("%4s",$(i))) } print}'
2001:0000:0000:0000:0000:0000:0000:0001
$ echo 2001:1:1:1:1:1:1:1 | awk -v OFS=":" -F: '{j=NF;for(i=8;i>=1;i--) {if (!$(i) && j<i) {if($(j)) {$(i)=$(j);$(j)="";j--} else $(i)=0} $(i)=gensub(" ","0","g",sprintf("%4s",$(i)))
} print}'
2001:0001:0001:0001:0001:0001:0001:0001
$ echo 2001:1:1::1:1:1:1 | awk -v OFS=":" -F: '{j=NF;for(i=8;i>=1;i--) {if (!$(i) && j<i) {if($(j)) {$(i)=$(j);$(j)="";j--} else $(i)=0} $(i)=gensub(" ","0","g",sprintf("%4s",$(i)))
} print}'
2001:0001:0001:0000:0001:0001:0001:0001
$ echo 2001:1:1:1:1:1:1:1 | awk -v OFS=":" -F: '{j=NF;for(i=8;i>=1;i--) {if (!$(i) && j<i) {if($(j)) {$(i)=$(j);$(j)="";j--} else $(i)=0} $(i)=gensub(" ","0","g",sprintf("%4s",$(i)))
} print}'
2001:0001:0001:0001:0001:0001:0001:0001
当它不是有效的 IPv6 地址时,不要指望它能够工作。没有验证作为 IPv6 超过 8 个字段或使用 :: 两次。
这是我的简单解决方案,适用于 Bash 和 BusyBox
sh
。
ip
是输入,expanded_ip
变量是输出。
# Add enough leading zeros to each field in the address so that each field is
# exactly 4 hexadecimal digits long.
expanded_ip="$(
printf '%s\n' "$ip" \
| sed -E 's/([0-9a-f]+)/000\1/g; s/0+([0-9a-f]{4})/\1/g'
)"
# If the address contains `::`, expand it into zeros.
if [[ "$expanded_ip" == *"::"* ]]; then
# The part of the address before the `::`.
ip_start="${expanded_ip/::*/}"
# The part of the address after the `::`.
ip_end="${expanded_ip/*::/}"
# Start with an IPv6 address of all zeros.
zeros="0000:0000:0000:0000:0000:0000:0000:0000"
# Slice out just the zeros that should replace the `::`.
zeros="${zeros:"${#ip_start}"}"
if [[ "${ip_end}" ]]; then
zeros="${zeros:0:-"${#ip_end}"}"
fi
# Join together the part before the `::`, the zeros replacing the `::`,
# and part after the `::` to get the final expanded IPv6 address.
expanded_ip="$ip_start$zeros$ip_end"
fi
作为奖励,由于这是我的用例,这里有另一个命令将扩展的 IP 转换为 PTR 记录地址:
# Remove the colons, separate each hexadecimal digit with a `.`, reverse it, and
# append `.ip6.arpa` to get the final PTR record name.
record_name="$(
printf '%s\n' "$expanded_ip" \
| sed -E 's/://g; s/(.)/.\1/g; s/^\.//' \
| rev
).ip6.arpa"