When an exception occurs on the server's port 445, mounting the CIFS file system in the network namespace fails, followed by a network namespace reference count leak issue. I've integrated the reproduction process into the test cases, and the specific scripts are as follows: ```bash #!/bin/bash # # This is a test library for shell. # [ -n "$TST_LIB_LOADED" ] && return 0 export TST_PASS=0 export TST_FAIL=0 export TST_BROK=0 export TST_WARN=0 export TST_CONF=0 export TST_COUNT=0 export TST_COLOR_ENABLED=1 export TST_LIB_LOADED=1 trap "tst_brk TBROK 'test interrupted'" INT tst_flag2color() { local ansi_color_blue='\033[1;34m' local ansi_color_green='\033[1;32m' local ansi_color_magenta='\033[1;35m' local ansi_color_red='\033[1;31m' local ansi_color_yellow='\033[1;33m' case "$1" in TPASS) printf $ansi_color_green;; TFAIL) printf $ansi_color_red;; TBROK) printf $ansi_color_red;; TWARN) printf $ansi_color_magenta;; TINFO) printf $ansi_color_blue;; TCONF) printf $ansi_color_yellow;; esac } tst_color_enabled() { [ $TST_COLOR_ENABLED -eq 1 ] && return 1 || return 0 } tst_print_colored() { tst_color_enabled local color=$? [ "$color" = "1" ] && tst_flag2color "$1" printf "$2" [ "$color" = "1" ] && printf '\033[0m' } tst_exit() { local ret=0 if [ $TST_FAIL -gt 0 ]; then ret=$((ret|1)) fi if [ $TST_BROK -gt 0 ]; then ret=$((ret|2)) fi if [ $TST_WARN -gt 0 ]; then ret=$((ret|4)) fi if [ $TST_CONF -gt 0 ]; then ret=$((ret|32)) fi cat >&2 << EOF Summary: passed $TST_PASS failed $TST_FAIL broken $TST_BROK skipped $TST_CONF warnings $TST_WARN EOF exit $ret } _tst_inc_res() { case "$1" in TPASS) TST_PASS=$((TST_PASS+1));; TFAIL) TST_FAIL=$((TST_FAIL+1));; TBROK) TST_BROK=$((TST_BROK+1));; TWARN) TST_WARN=$((TST_WARN+1));; TCONF) TST_CONF=$((TST_CONF+1));; TINFO) ;; *) tst_brk TBROK "Invalid res type '$1'";; esac } tst_res() { local res=$1 if type caller >/dev/null 2>&1;then caller_info=$(caller 2) || caller_info=$(caller 1) || caller_info=$(caller 0) caller_info=$(echo "${caller_info}" | awk 'info=$NF":"$1 {print info}') fi shift tst_color_enabled local color=$? TST_COUNT=$(($TST_COUNT+1)) _tst_inc_res "$res" printf "$(date) $TST_ID $TST_COUNT ${caller_info} " tst_print_colored $res "$res: " echo -e "$@" } tst_brk() { local res=$1 shift tst_res "$res" "$@" tst_exit } tst_begin_test() { if [ -w /dev/kmsg ]; then export tst_date_time=`date +"%F %T%N"` echo "run test at $tst_date_time" > /dev/kmsg fi } _dmesg_since_test_start() { if which tac > /dev/null 2>&1; then dmesg -T | tac | sed -ne "0,\#run test at $tst_date_time#p" | tac else dmesg -T >/dev/null 2>&1 && dmesg_cmd="dmesg -T" || dmesg_cmd="dmesg" tac_cmd="awk '{ lines[NR] = \$0 } END { for (i = NR; i > 0; i--) print lines[i] }'" eval "${dmesg_cmd} | ${tac_cmd} | sed -ne \"1,\#run test at ${tst_date_time}#p\" | ${tac_cmd}" fi } tst_check_dmesg_for() { keywords=$@ if [ -z "$keywords" ];then tst_res TWARN "tst_check_dmesg_for parameter is null" else _dmesg_since_test_start | grep -E -q "$keywords" fi } _tst_expect_pass() { local fnc="$1" shift eval $@ if [ $? -eq 0 ]; then tst_res TPASS "$@ passed as expected" return 0 else $fnc TFAIL "$@ failed unexpectedly" return 1 fi } _tst_expect_fail() { local fnc="$1" shift eval $@ if [ $? -ne 0 ]; then tst_res TPASS "$@ failed as expected" return 0 else $fnc TFAIL "$@ pass unexpectedly" return 1 fi } tst_expect_pass() { _tst_expect_pass tst_res "$@" } tst_expect_fail() { _tst_expect_fail tst_res "$@" } # check dmesg log for WARNING/Oops/etc. tst_check_dmesg() { ret=$(_dmesg_since_test_start | grep -E -q \ -e "kernel BUG at" \ -e "WARNING:" \ -e "BUG:" \ -e "WARNING:" \ -e "Oops:" \ -e "possible recursive locking detected" \ -e "Internal error" \ -e "suspicious RCU usage" \ -e "possible circular locking dependency detected" \ -e "general protection fault" \ -e "BUG.*remaining" \ -e "UBSAN" \ -e "KASAN" \ -e "Call trace") if [ $? -eq 0 ]; then tst_res TWARN "_check_dmesg: something found in dmesg\n$(_dmesg_since_test_start)" fi } # generate random number # # tst_rand random between [0, 32767] # tst_rand $1 $2 random between [$1, $2] ($2-$1 should less than 32767) tst_rand_int() { if [ $# -eq 0 ]; then echo $RANDOM elif [ $# -eq 2 ]; then local min=$1 local max=$2 local offset=$(($max-$min+1)) echo $(($RANDOM%$offset+$min)) else tst_res TWARN "tst_rand only accepts 0 or 2 parameters" fi } if [ -z "$TST_ID" ]; then _tst_filename=$(basename $0) || \ tst_brk TCONF "Failed to set TST_ID from \$0 ('$0')" TST_ID=${_tst_filename%%.*} fi export TST_ID="$TST_ID" # ------------------------------------------------------------------------ workdir=$(pwd) tmp_dir="${workdir}/tmp" kernel_version=$(uname -r) smb_conf=${tmp_dir}/smb.conf test_dir=${tmp_dir}/share mount_dir=${tmp_dir}/mp trap "do_post_2" exit stop_smbd() { pidof smbd >/dev/null 2>&1 && killall smbd count=1 while true; do pidof smbd >/dev/null 2>&1 || return 0 sleep 2 count=$((count + 1)) if [ "${count}" -ge 10 ]; then echo "smbd pid:$(pidof smbd)" return 1 fi done } prepare_cifs() { local ret=0 if ! type smbd >/dev/null 2>&1; then echo "smbd is not found" return 1 fi mkdir -p "${test_dir}" cat >>"${smb_conf}" <<EOF [global] workgroup = SAMBA security = user map to guest = Bad User [share] path = ${test_dir} browseable = yes read only = no guest ok = yes force user = root read only = no EOF stop_smbd || return 1 # 启动smbd smbd -s "${smb_conf}" -D # 添加防火墙规则,阻止访问445端口 iptables -A INPUT -p tcp --dport 445 -j REJECT } do_pre() { tst_res TINFO "[start]do pre" tst_expect_pass prepare_cifs tst_res TINFO "[end]do_pre" } do_test() { tst_res TINFO "[start]do test" local net_ns=cifs_ns local test_veth=veth1 local test_veth_peer=veth2 local server_ip=192.168.11.2 mkdir -p "${mount_dir}" ip netns add ${net_ns} ip link add ${test_veth} type veth peer name ${test_veth_peer} ip link set ${test_veth} up ip addr add ${server_ip}/24 dev ${test_veth} ip link set ${test_veth_peer} netns ${net_ns} nsenter --net=/run/netns/${net_ns} bash -c " ip link set lo up ip link set ${test_veth_peer} up ip addr add 192.168.11.3/24 dev ${test_veth_peer} # 挂载CIFS mount -t cifs //${server_ip}/share ${mount_dir} -o username=nobody,guest " tst_expect_fail "grep ${mount_dir} /proc/mounts" sleep 5 tst_expect_pass "ip netns del ${net_ns}" tst_expect_fail "ip a | grep veth1" tst_res TINFO "[end]do_test" } do_post_1() { tst_res TINFO "[start]do post" stop_smbd type smbd >/dev/null 2>&1 && smbd -D [ -d "${tmp_dir}" ] && tst_expect_pass rm -rf "${tmp_dir}" # 删除防火墙规则 iptables -D INPUT -p tcp --dport 445 -j REJECT tst_res TINFO "[end]do_post" } do_post_2() { do_post_1 tst_check_dmesg tst_exit } run_testcase() { tst_begin_test do_pre [ "${TST_FAIL}" -ne 0 ] && exit 1 do_test } run_testcase ``` When the test case fails, you can use the following command to check for leaked network namespaces: ip a | grep -A 5 veth1 ``` # ip a | grep -A 5 veth1 11: veth1@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 92:ad:c9:2b:dd:ad brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 192.168.11.2/24 scope global veth1 valid_lft forever preferred_lft forever inet6 fe80::90ad:c9ff:fe2b:ddad/64 scope link proto kernel_ll valid_lft forever preferred_lft forever ```
The problem process is as follows: ``` mount.cifs cifsd cifs_do_mount cifs_mount cifs_mount_get_session cifs_get_tcp_session get_net() /* First get net */ ip_connect /* Try port 445 */ generic_ip_connect get_net() ->connect() /* failed */ put_net() /* This call is balanced */ /* Try port 139 */ generic_ip_connect get_net() /* Missing matching put_net() for this get_net() */ cifs_get_smb_ses cifs_negotiate_protocol smb2_negotiate SMB2_negotiate cifs_send_recv wait_for_response cifs_demultiplex_thread cifs_read_from_socket cifs_readv_from_socket cifs_reconnect cifs_abort_connection sock_release(); server->ssocket = NULL; /* Missing put_net() here. */ generic_ip_connect get_net() ->connect() /* Failed */ put_net() sock_release(); server->ssocket = NULL; free_rsp_buf ... clean_demultiplex_info /* It's only called once here. */ put_net() ```
For my test case, the following kernel config is necessary: CONFIG_CIFS CONFIG_VETH CONFIG_NFT_COUNTER CONFIG_NF_REJECT_IPV4 CONFIG_NFT_COMPAT CONFIG_NF_TABLES CONFIG_NETFILTER_NETLINK