Adds explicit IPV6 IPV4 choices arguments
This commit is contained in:
parent
e870190ae8
commit
60a8cb4b93
8 changed files with 198 additions and 72 deletions
1
check.sh
1
check.sh
|
|
@ -155,6 +155,7 @@ TC_RUN() {
|
|||
2)$cmd "$1" "$2";;
|
||||
3)$cmd "$1" "$2" "$3";;
|
||||
4)$cmd "$1" "$2" "$3" "$4";;
|
||||
5)$cmd "$1" "$2" "$3" "$4" "$5";;
|
||||
*)fail "To many arguments for $cmd"
|
||||
esac
|
||||
|
||||
|
|
|
|||
|
|
@ -14,15 +14,21 @@
|
|||
#along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
check_git_repo() {
|
||||
ipv_arg=$2
|
||||
case "$ipv_arg" in
|
||||
ipv4)ipv=-4;;
|
||||
ipv6)ipv=-6;;
|
||||
default)ipv="";
|
||||
esac
|
||||
tmpdir=$(mktemp -d -t check_git.XXXXXXXXX)
|
||||
git clone "$1" "$tmpdir" 2>/dev/null 1>/dev/null
|
||||
git clone $ipv "$1" "$tmpdir" 2>/dev/null 1>/dev/null
|
||||
res="$?"
|
||||
rm -Rf "$tmpdir"
|
||||
if [ "$res" -ne 0 ]
|
||||
then
|
||||
fail "unable to clone git repo '$1'"
|
||||
fail "unable to clone git repo '$1' $ipv_arg"
|
||||
else
|
||||
success "git repo '$1' cloned'"
|
||||
success "git repo '$1' $ipv_arg cloned'"
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,59 +21,82 @@
|
|||
_check_http_status() {
|
||||
# $1 url
|
||||
# $2 status
|
||||
# [$3] ipv4|ipv6
|
||||
# $* curl options
|
||||
url=$1
|
||||
shift 1
|
||||
expt=$1
|
||||
if [ -z "$1" ]
|
||||
then
|
||||
expt=200
|
||||
fi
|
||||
shift 1
|
||||
case "$1" in
|
||||
ipv4) ipv=-4
|
||||
ipv_arg=" ipv4"
|
||||
shift 1
|
||||
;;
|
||||
ipv6) ipv=-6
|
||||
ipv_arg=" ipv6"
|
||||
shift 1
|
||||
;;
|
||||
default)ipv=""
|
||||
ipv_arg=""
|
||||
;;
|
||||
esac
|
||||
|
||||
status=$(curl -s -o /dev/null -w "%{http_code}" "$@" "$url")
|
||||
status=$(curl $ipv -s -o /dev/null -w "%{http_code}" "$@" "$url")
|
||||
if [ "$status" -ne "$expt" ]
|
||||
then
|
||||
fail "Check http status $expt for $url : $status returned"
|
||||
fail "Check http status $expt for$ipv_arg $url : $status returned"
|
||||
elif [ "$verbose" -gt 0 ]
|
||||
then
|
||||
success "Check http status $status for $url"
|
||||
success "Check http status $status for$ipv_arg $url"
|
||||
fi
|
||||
}
|
||||
|
||||
check_http_status() {
|
||||
# $1 url
|
||||
# $2 status
|
||||
_check_http_status "$1" "$2"
|
||||
_check_http_status "$1" "$2" "$3"
|
||||
}
|
||||
|
||||
check_http_200() {
|
||||
_check_http_status "$1" 200
|
||||
_check_http_status "$1" 200 "$2"
|
||||
}
|
||||
|
||||
check_https_cert() {
|
||||
# Check that SSL Cert is valid
|
||||
# $1 URL
|
||||
status=$(curl -s -o /dev/null -w "%{http_code}" "https://$1")
|
||||
# $2 ipv4|ipv6
|
||||
ipv_arg=$2
|
||||
case "$ipv_arg" in
|
||||
ipv4)ipv=-4;;
|
||||
ipv6)ipv=-6;;
|
||||
default)ipv="";;
|
||||
esac
|
||||
status=$(curl $ipv -s -o /dev/null -w "%{http_code}" "https://$1")
|
||||
rep=$?
|
||||
if [ "$rep" -eq 0 ]
|
||||
then
|
||||
success "https://$1 cert verified"
|
||||
success "https:$ipv_arg//$1 cert verified"
|
||||
return
|
||||
fi
|
||||
status=$(curl -k -s -o /dev/null -q "%{http_code}" "https://$1")
|
||||
status=$(curl $ipv -k -s -o /dev/null -q "%{http_code}" "https://$1")
|
||||
rep=$?
|
||||
if [ "$rep" -eq 0 ]
|
||||
then
|
||||
fail "https://$1 cert invalid"
|
||||
fail "https:$ipv_arg//$1 cert invalid"
|
||||
else
|
||||
err "Unable to curl https://$1"
|
||||
err "Unable to curl https:$ipv_arg//$1"
|
||||
fi
|
||||
}
|
||||
|
||||
check_html_title() {
|
||||
url="$1"
|
||||
expt="$2"
|
||||
ipv_arg=$3
|
||||
case "$ipv_arg" in
|
||||
ipv4)ipv=-4;;
|
||||
ipv6)ipv=-6;;
|
||||
default)ipv="";;
|
||||
esac
|
||||
|
||||
tmpxsl=$(mktemp -t XSL.XXXXXXXXXXX)
|
||||
echo '<?xml version="1.0"?>
|
||||
|
|
@ -87,7 +110,7 @@ xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
|||
|
||||
tmphtml=$(mktemp -t html.XXXXXXXXX)
|
||||
|
||||
curl --silent "$url" > "$tmphtml"
|
||||
curl $ipv --silent "$url" > "$tmphtml"
|
||||
if title=$(xsltproc --html --novalid "$tmpxsl" "$tmphtml" 2>/dev/null)
|
||||
then
|
||||
:
|
||||
|
|
@ -97,9 +120,9 @@ xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
|||
|
||||
if [ "$title" = "$expt" ]
|
||||
then
|
||||
success "$url HTML title is '$expt'"
|
||||
success "$url $ipv_arg HTML title is '$expt'"
|
||||
else
|
||||
fail "$url HTML title is '$title' but '$expt' expected"
|
||||
fail "$url $ipv_arg HTML title is '$title' but '$expt' expected"
|
||||
fi
|
||||
|
||||
rm "$tmpxsl" "$tmphtml" 2>/dev/null
|
||||
|
|
|
|||
|
|
@ -16,16 +16,25 @@
|
|||
check_smtp() {
|
||||
hostname=$1
|
||||
port=$2
|
||||
ipv_arg=$3
|
||||
|
||||
if [ -z "$port" ]
|
||||
then
|
||||
port=587
|
||||
fi
|
||||
code=$(nc -q0 "$hostname" "$port" < /dev/null | cut -d " " -f1)
|
||||
|
||||
case "$ipv_arg" in
|
||||
ipv4)ipv=-4;;
|
||||
ipv6)ipv=-6;;
|
||||
*)ipv="";;
|
||||
esac
|
||||
|
||||
code=$(nc $ipv -q0 "$hostname" "$port" < /dev/null | cut -d " " -f1)
|
||||
if [ "$code" = "220" ]
|
||||
then
|
||||
success "SMTP@$hostname server replied 220 status"
|
||||
success "SMTP@$hostname $ipv_arg server replied 220 status"
|
||||
else
|
||||
fail "Unexpected reply from smtp@$hostname '$code'"
|
||||
fail "Unexpected reply from smtp@$hostname $ipv_arg '$code'"
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
@ -33,33 +42,50 @@ check_smtp_id() {
|
|||
hostname=$1
|
||||
idstr=$2
|
||||
port=$3
|
||||
ipv=$4
|
||||
|
||||
if [ -z "$port" ]
|
||||
then
|
||||
port=587
|
||||
fi
|
||||
rep=$(nc -q0 "$hostname" "$port" < /dev/null)
|
||||
|
||||
case "$ipv_arg" in
|
||||
ipv4)ipv=-4;;
|
||||
ipv6)ipv=-6;;
|
||||
*)ipv="";;
|
||||
esac
|
||||
|
||||
rep=$(nc $ipv -q0 "$hostname" "$port" < /dev/null)
|
||||
if echo "$rep" | grep -e "^220 $idstr" > /dev/null
|
||||
then
|
||||
success "SMTP@$hostname server replie '$idstr'"
|
||||
success "SMTP@$hostname $ipv_arg server replie '$idstr'"
|
||||
else
|
||||
fail "Unexpected reply from smtp@$hostname '$rep'"
|
||||
fail "Unexpected reply from smtp@$hostname $ipv_arg '$rep'"
|
||||
fi
|
||||
}
|
||||
|
||||
check_imaps() {
|
||||
hostname=$1
|
||||
port=$2
|
||||
ipv=$3
|
||||
if [ -z "$port" ]
|
||||
then
|
||||
port=993
|
||||
fi
|
||||
rep=$(echo "01 LOGOUT" | openssl s_client -quiet -verify_quiet -connect "${hostname}:$port" -servername "$hostname")
|
||||
|
||||
case "$ipv_arg" in
|
||||
ipv4)ipv=-4;;
|
||||
ipv6)ipv=-6;;
|
||||
*)ipv="";;
|
||||
esac
|
||||
|
||||
rep=$(echo "01 LOGOUT" | openssl s_client $ipv -quiet -verify_quiet -connect "${hostname}:$port" -servername "$hostname")
|
||||
|
||||
if echo "$rep" | grep "^01 OK" > /dev/null
|
||||
then
|
||||
success "IMAPS@$hostname '$(echo "$rep" | grep "^01 OK")'"
|
||||
success "IMAPS@$hostname $ipv_arg '$(echo "$rep" | grep "^01 OK")'"
|
||||
else
|
||||
fail "Unexpected reply from imaps@$hostname '$rep'"
|
||||
fail "Unexpected reply from imaps@$hostname $ipv_arg '$rep'"
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,14 +13,11 @@
|
|||
#You should have received a copy of the GNU General Public License
|
||||
#along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
check_ping() {
|
||||
hostname=$1
|
||||
count=$2
|
||||
proto="$3"
|
||||
if [ -z "$count" ]
|
||||
then
|
||||
count=5
|
||||
fi
|
||||
_check_ping() {
|
||||
expt=$1
|
||||
hostname=$2
|
||||
proto=$3
|
||||
count=$4
|
||||
case "$proto" in
|
||||
ipv4)
|
||||
protoping="ping -4";;
|
||||
|
|
@ -29,15 +26,38 @@ check_ping() {
|
|||
*)
|
||||
protoping="ping";;
|
||||
esac
|
||||
if [ -z "$count" ]
|
||||
then
|
||||
count=5
|
||||
fi
|
||||
|
||||
if $protoping -i 0.2 -c "$count" "$hostname" >/dev/null
|
||||
then
|
||||
success "successfully send $count ping to '$hostname'"
|
||||
if [ "$expt" = "ping" ]
|
||||
then
|
||||
success "successfully send $count ping $proto to '$hostname'"
|
||||
else
|
||||
fail "$hostname replied to $count ping $proto"
|
||||
fi
|
||||
else
|
||||
fail "unable to ping '$hostname'"
|
||||
if [ "$expt" = "ping" ]
|
||||
then
|
||||
fail "unable to ping '$hostname'"
|
||||
else
|
||||
success "unable to ping '$hostname'"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
check_ping() {
|
||||
_check_ping "ping" "$1" "$2" "$3" "$4"
|
||||
}
|
||||
|
||||
check_noping() {
|
||||
_check_ping "noping" "$1" "$2" "$3" "$4"
|
||||
}
|
||||
|
||||
|
||||
check_dns() {
|
||||
hostname=$1
|
||||
field=$2
|
||||
|
|
|
|||
|
|
@ -20,14 +20,26 @@
|
|||
check_ssh_nc() {
|
||||
host=$1
|
||||
port=$2
|
||||
ipv=$3
|
||||
|
||||
if [ -z "$port" ]
|
||||
then
|
||||
port=22
|
||||
fi
|
||||
if [ -z "$ipv" ]
|
||||
then
|
||||
ipv=ipv4
|
||||
fi
|
||||
case "$ipv" in
|
||||
ipv4)rep="$(nc -4 -w1 "$host" "$port" </dev/null)"
|
||||
res=$?
|
||||
;;
|
||||
ipv6)rep="$(nc -6 -w1 "$host" "$port" </dev/null)"
|
||||
res=$?
|
||||
;;
|
||||
default)fail "Bad ip version $ipv";;
|
||||
esac
|
||||
|
||||
rep="$(nc -w1 "$host" "$port" </dev/null)"
|
||||
res=$?
|
||||
if [ "$res" -ne "0" ]
|
||||
then
|
||||
fail "Netcat unable to connect to $host:$port"
|
||||
|
|
@ -46,6 +58,13 @@ check_ssh_key() {
|
|||
testkey="$2"
|
||||
keytype="$3"
|
||||
port="$4"
|
||||
ipv_arg="$5"
|
||||
|
||||
case "$ipv" in
|
||||
ipv4)ipv=-4;;
|
||||
ipv6)ipv=-6;;
|
||||
*)ipv="";;
|
||||
esac
|
||||
|
||||
if [ -z "$port" ]
|
||||
then
|
||||
|
|
@ -57,7 +76,7 @@ check_ssh_key() {
|
|||
keytype="rsa"
|
||||
fi
|
||||
|
||||
key=$(ssh-keyscan -p $port -t "$keytype" "$host" 2>/dev/null | cut -d " " -f3)
|
||||
key=$(ssh-keyscan $ipv -p $port -t "$keytype" "$host" 2>/dev/null | cut -d " " -f3)
|
||||
|
||||
if [ -z "$key" ]
|
||||
then
|
||||
|
|
@ -65,10 +84,10 @@ check_ssh_key() {
|
|||
return
|
||||
elif [ "$key" = "$testkey" ]
|
||||
then
|
||||
success "OpenSSH $host:$port key is $testkey"
|
||||
success "OpenSSH $host:$port $ipv_arg key is $testkey"
|
||||
return
|
||||
else
|
||||
fail "OpenSSH $host:$port missmatch : "
|
||||
fail "OpenSSH $host:$port $ipv_arg missmatch : "
|
||||
logdate ERR "Expected : $testkey" 1
|
||||
logdate ERR "Received : $key" 1
|
||||
return
|
||||
|
|
|
|||
|
|
@ -17,15 +17,16 @@ check_audiostream() {
|
|||
# Uses one of mplayer or mpv to retrieve a small amount of stream
|
||||
# and attempt to decode it.
|
||||
# $1 Stream URL
|
||||
# $2 ipv4|ipv6
|
||||
|
||||
MPLAYER="$(which mplayer)"
|
||||
MPV="$(which mpv)"
|
||||
if [ -x "$MPLAYER" ]
|
||||
then
|
||||
check_audiostream_mplayer "$1"
|
||||
check_audiostream_mplayer "$1" "$2"
|
||||
elif [ -x "$MPV" ]
|
||||
then
|
||||
check_audiostream_mpv "$1"
|
||||
check_audiostream_mpv "$1" "$2"
|
||||
else
|
||||
fail "Unable to find mplayer nor mpv"
|
||||
fi
|
||||
|
|
@ -34,16 +35,23 @@ check_audiostream() {
|
|||
check_audiostream_mplayer() {
|
||||
# Uses mplayer to fetch 128Kb (by default) of stream
|
||||
# $1 Stream URL
|
||||
# $2 size in kb without the kb suffix
|
||||
# $3 mplayer path
|
||||
# $2 ipv4|ipv6|auto
|
||||
# $3 size in kb without the kb suffix
|
||||
# $4 mplayer path
|
||||
|
||||
tmpfile=$(mktemp -t check_audiostream.XXXXXXXXX)
|
||||
sz="$2"
|
||||
ipv_arg=$2
|
||||
case "$ipv_arg" in
|
||||
ipv4)ipv=-prefer-ipv4;;
|
||||
ipv6)ipv=-prefer-ipv6;;
|
||||
default)ipv="";;
|
||||
esac
|
||||
sz="$3"
|
||||
if [ -z "$sz" ]
|
||||
then
|
||||
sz="128"
|
||||
fi
|
||||
MPLAYER="$3"
|
||||
MPLAYER="$4"
|
||||
if [ -z "$MPLAYER" ]
|
||||
then
|
||||
MPLAYER="$(which mplayer)"
|
||||
|
|
@ -54,39 +62,46 @@ check_audiostream_mplayer() {
|
|||
|
||||
if [ "$verbose" -gt 1 ]
|
||||
then
|
||||
logdate INFO "$tc_name: Running mplayer on '$1' for $sz_kb" 3
|
||||
logdate INFO "$tc_name: Running mplayer on $ipv_arg '$1' for $sz_kb" 3
|
||||
fi
|
||||
|
||||
$MPLAYER -endpos "$sz_kb" -ao "pcm:file=$tmpfile" "$1" 1>/dev/null 2>/dev/null
|
||||
$MPLAYER $ipv -endpos "$sz_kb" -ao "pcm:file=$tmpfile" "$1" 1>/dev/null 2>/dev/null
|
||||
res="$?"
|
||||
bytes=$(du -b "$tmpfile" | cut -f1)
|
||||
rm "$tmpfile" 2>/dev/null
|
||||
|
||||
if [ "$bytes" -lt $expt_sz ]
|
||||
then
|
||||
fail "mplayer retrieved ${bytes}B of stream instead of expected $sz_kb"
|
||||
fail "mplayer $ipv_arg retrieved ${bytes}B of $ipv_arg stream instead of expected $sz_kb"
|
||||
return
|
||||
fi
|
||||
if [ "$res" -eq 0 ]
|
||||
then
|
||||
success "mplayer retrieved $sz of stream on $1"
|
||||
success "mplayer retrieved $sz of $ipv_arg stream on $1"
|
||||
else
|
||||
fail "mplayer failed to retrieve stream on $1"
|
||||
fail "mplayer failed to retrieve $ipv_arg stream on $1"
|
||||
fi
|
||||
}
|
||||
|
||||
check_audiostream_mpv() {
|
||||
# Uses mpv to fetch 4s of audio stream
|
||||
# $1 Stream URL
|
||||
# $2 ipv4|ipv6|auto
|
||||
# $2 time of stream to fetch
|
||||
# $3 mpv path
|
||||
|
||||
time="$2"
|
||||
ipv_arg=$2 # NOT SUPPORTED BY MPV ??
|
||||
case "$ipv_arg" in
|
||||
ipv4)ipv=-prefer-ipv4;;
|
||||
ipv6)ipv=-prefer-ipv6;;
|
||||
default)ipv="";;
|
||||
esac
|
||||
time="$3"
|
||||
if [ -z "$time" ]
|
||||
then
|
||||
time="4"
|
||||
fi
|
||||
MPV="$3"
|
||||
MPV="$4"
|
||||
if [ -z "$MPV" ]
|
||||
then
|
||||
MPV="$(which mpv)"
|
||||
|
|
|
|||
|
|
@ -34,12 +34,19 @@ __xmpp_probe() {
|
|||
_xmpp_probe() {
|
||||
serv=$1
|
||||
port=$2
|
||||
timeout=$3
|
||||
type=$4
|
||||
ipv_arg=$3
|
||||
timeout=$4
|
||||
type=$5
|
||||
|
||||
case "$ipv_arg" in
|
||||
ipv6)ipv=-6;;
|
||||
ipv4)ipv=-4;;
|
||||
default)ipv="";;
|
||||
esac
|
||||
|
||||
tmpres=$(mktemp -t xmpp_probe.XXXXXXXXX)
|
||||
|
||||
echo "$(__xmpp_probe "$serv" "$timeout" "$type"| nc -q2 "$serv" "$port")</stream:stream>" | xmllint --format - > "$tmpres"
|
||||
echo "$(__xmpp_probe "$serv" "$timeout" "$type"| nc $ipv -q2 "$serv" "$port")</stream:stream>" | xmllint --format - > "$tmpres"
|
||||
if [ "$verbose" -gt 2 ]
|
||||
then
|
||||
sed -e "s/^/$(datetime) [ DEBUG] Recv : /" >&2 < "$tmpres"
|
||||
|
|
@ -54,7 +61,8 @@ _check_xmpp_ns() {
|
|||
expt_port=$3
|
||||
dnsq="_xmpp-${4}._tcp.${ns1}"
|
||||
rep="$(dig "$dnsq" srv +short | cut -d" " -f3,4)"
|
||||
if [ "$rep" = "$expt_port ${expt_ns}." ]
|
||||
#if [ "$rep" = "$expt_port ${expt_ns}." ]
|
||||
if echo "$rep" | grep "${expt_ns}.$" >/dev/null
|
||||
then
|
||||
success "$dnsq = '$rep'"
|
||||
else
|
||||
|
|
@ -73,8 +81,9 @@ check_xmpp_client_ns() {
|
|||
_check_xmpp_server() {
|
||||
serv=$1
|
||||
port=$2
|
||||
timeout=$3
|
||||
type=$4
|
||||
ipv=$3
|
||||
timeout=$4
|
||||
type=$5
|
||||
|
||||
if [ -z "$port" ]
|
||||
then
|
||||
|
|
@ -92,10 +101,10 @@ _check_xmpp_server() {
|
|||
then
|
||||
tpe="S2S"
|
||||
fi
|
||||
logdate INFO "$tc_name: Connecting to XMPP $serv $tpe port $port (timeout=${timeout}s)" 3
|
||||
logdate INFO "$tc_name: Connecting to XMPP $serv $tpe port $port $ipv (timeout=${timeout}s)" 3
|
||||
fi
|
||||
|
||||
stream=$(_xmpp_probe "$serv" "$port" "$timeout" "$type"| head -n2 | tail -n1)
|
||||
stream=$(_xmpp_probe "$serv" "$port" "$ipv" "$timeout" "$type"| head -n2 | tail -n1)
|
||||
if [ -z "$stream" ]
|
||||
then
|
||||
fail "Empty reply from $serv:$port"
|
||||
|
|
@ -111,36 +120,43 @@ _check_xmpp_server() {
|
|||
|
||||
if echo "$infos" | grep "jabber:$type" >/dev/null
|
||||
then
|
||||
success "Successfully connected to XMPP $type $serv:$port"
|
||||
success "Successfully connected to XMPP $type $serv:$port $ipv"
|
||||
else
|
||||
fail "Unexpected reply from $serv:$port : $infos"
|
||||
fail "Unexpected reply from $serv:$port $ipv : $infos"
|
||||
fi
|
||||
}
|
||||
|
||||
check_xmpp_server_client() {
|
||||
_check_xmpp_server "$1" "$2" "$3" "client"
|
||||
_check_xmpp_server "$1" "$2" "$3" "$4" "client"
|
||||
}
|
||||
|
||||
check_xmpp_server_s2s() {
|
||||
_check_xmpp_server "$1" "$2" "$3" "server"
|
||||
_check_xmpp_server "$1" "$2" "$3" "$4" "server"
|
||||
}
|
||||
|
||||
check_xmpp_ssl() {
|
||||
serv=$1
|
||||
port=$2
|
||||
ipv_arg=$3
|
||||
|
||||
if [ -z "$port" ]
|
||||
then
|
||||
port=5222
|
||||
fi
|
||||
|
||||
openssl s_client -connect "$serv:$port" </dev/null -starttls xmpp >/dev/null 2>/dev/null
|
||||
case "$ipv_arg" in
|
||||
ipv6)ipv=-6;;
|
||||
ipv4)ipv=-4;;
|
||||
default)ipv="";;
|
||||
esac
|
||||
|
||||
openssl s_client $ipv -connect "$serv:$port" </dev/null -starttls xmpp >/dev/null 2>/dev/null
|
||||
rep=$?
|
||||
if [ "$rep" -eq 0 ]
|
||||
then
|
||||
success "Openssl successfully negociating XMPP ssl with $serv:$port"
|
||||
success "Openssl successfully negociating XMPP ssl with $serv:$port $ipv_arg"
|
||||
else
|
||||
fail "Openssl failed negociating XMPP ssl with $serv:$port"
|
||||
fail "Openssl failed negociating XMPP ssl with $serv:$port $ipv_arg"
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue