前言
需求
项目现场遇到多节点随机访问的集群环境,需要tcpdump抓包定位问题,由于节点linux服务器数量很多,每个服务器登录ssh后台使用tcpdump命令捕获网络报文,非常麻烦,需要有工具或者脚本 能在本地某个服务器上发起ssh请求,并向目标服务器发送抓包命令。
解决思路
批量主机的运维,首先想到的是ansible。众所周知ansible的强大之处,必然是可以实现的(ansible+playbook方式很容易),但是考虑到ansible的安装需要依赖python ,并且安装需要有epel的yum资源 ,或者本地需要打包一堆的rpm包 通过rpm方式安装,不利于在不同现场的易用性,于是乎想到linux主机上几乎都能使用的shell脚本来实现。
刚开始希望通过 ssh命令 并且携带tcpdump指令和参数的方式,然后通过for循环,遍历要执行命令的主机列表,后来发现这种方式需要先配置免密登录,对于超多的节点服务器来说也不是很友好,而且很多主机并不是root用户直接ssh后台的,还需要切换用户去执行,,并且ssh命令 无法返回执行的结果,看不到执行是否成功,本地也无法有日志记录。
于是乎,在度娘的搜索中找到一个比较古老的方案,通过在/bin/bash脚本中插入/usr/bin/expect的代码段,使用spawn+expect命令来实现脚本中交互式的输入 登录目标主机ssh,输入需要的指令,并在本地展示执行结果,实现ssh到目标主机的批量抓包和日志收集
且expect的安装依赖少,很多情况下主机操作系统会携带,便于不同环境间的复用
脚本内容
核心片段
#构建一个ssh函数,支持脚本中判断服务端的返回字段 并发送预设的字符串内容给服务端
my_ssh()
{
#插入expect的代码段
/usr/bin/expect<<-EOF
#spawn的方式登录目标主机
spawn ssh $ssh_username@$ssh_host -p $ssh_port
set timeout 2.5 #等待目标主机返回时长
#以下是期待返回的内容,不同的返回内容会send 不同的输入,这里就列举了一些常见的ssh动作后的返回
expect {
"*yes/no*" { send "yes\\r";exp_continue;}
"*password:" { send "$ssh_passwd\\r";exp_continue;}
"*密码:" { send "$ssh_passwd\\r" }
}
#su到root用户 并根据提示输入密码 解决ssh时候为非root用户
expect "#"
send "su – root\\r"
expect {
"*Password:" { send "$root_passwd\\r";exp_continue;}
"*密码:" { send "$ssh_passwd\\r"; exp_continue;}
"#" { send "mkdir -p $cap_data_dir&&chmod 777 $cap_data_dir\\r" }
}
#发送需要的命令
expect {
"#" { send "$cmd\\r" }
}
expect eof
EOF
}
脚本目录结构
脚本有两部分 一部分start_auto_cap_4.1.sh控制启动 停止和用户交互 ,一部分 auto_ssh_4.1.sh为登录目标主机后台执行命令
配置文件存在config目录的auto_cap.config文件中
Dependency_Packages下存放着不同的linux操作系统依赖的expect的rpm包
log目录为脚本运行日志,可以查看历史执行情况和目标主机的返回结果
配置文件内容
#ssh用户名
user=hadmin
#ssh登录密码
passwd=Gongan@2023
#ssh登录端口
port=22
#目标主机root用户密码
root_passwd=Gongan@2023
#目标主机地址段配置
hosts_list=( 10.56.19.86 10.56.19.{111..113} 10.56.19.173 10.56.19.77 )
#远程主机抓包存储目录
cap_data_dir="/cloud/cap_data"
#抓包过滤条件(tcpdump过滤器语法一致)
cap_fliter=""
#抓包过滤网卡(默认any,可根据网卡名称修改)
cap_network_card="any"
#抓包自循时间(秒)
cycle_time=1200
#抓包切片大小(MB)
cut_size=300
#脚本日志存储路径(默认为脚本路径下的log目录)
log_dir="./log"
log_file="./log/auto_cmd_$(date +"%F").log"
脚本完整内容
auto_ssh_4.1.sh
#/bin/bash
CAP_SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
if [ ! -e $CAP_SCRIPT_DIR/config/auto_cap.config ];then
echo "Config file not exit ,Please Check ! Script Exit ! "
exit 1
fi
source $CAP_SCRIPT_DIR/config/auto_cap.config
ssh_host=$1
ssh_port=$port
ssh_username=$user
ssh_passwd=$passwd
action=$2
if [ $# -eq 0 ];
then
echo "Please check your args!";exit
elif [ $# -ne 2 ];
then
echo "Too many args!"
fi
cmd_keyword="tcpdump"
log_dir="./log"
log_file="$log_dir/auto_cmd_$(date +"%F").log"
if [ $# -eq 0 ];
then
echo "Please check your args!";exit
fi
if [ $action -eq 1 ];
then
cmd="nohup tcpdump -i $cap_network_card -w $cap_data_dir/$ssh_host.pcap -s 0 -v -G $cycle_time -C $cut_size >/dev/null 2>&1 & \\r"
cmd_keyword="tcpdump"
elif [ $action -eq 0 ];
then
cmd="pidof tcpdump | xargs kill -9 \\r"
cmd_keyword="tcpdump"
elif [ $action -eq 2 ];
then
cmd="find $cap_data_dir -name $ssh_host.pcap* -delete"
cmd_keyword="delete"
else
echo "illegal action!";exit
fi
function log_info()
{
echo "$(date +"%F %T") [INFO] $*" >> $log_file
}
my_ssh()
{
/usr/bin/expect<<-EOF
spawn ssh $ssh_username@$ssh_host -p $ssh_port
set timeout 2.5
expect {
"*yes/no*" { send "yes\\r";exp_continue;}
"*password:" { send "$ssh_passwd\\r";exp_continue;}
"*密码:" { send "$ssh_passwd\\r" }
}
expect "#"
send "su – root\\r"
expect {
"*Password:" { send "$root_passwd\\r";exp_continue;}
"*密码:" { send "$ssh_passwd\\r"; exp_continue;}
"#" { send "mkdir -p $cap_data_dir&&chmod 777 $cap_data_dir\\r" }
}
expect {
"#" { send "$cmd\\r" }
}
expect eof
EOF
}
if [ ! -d $log_dir ];then
mkdir $log_dir
fi
stime=`date +"%Y-%m-%d %H:%M:%S"`
echo "[$stime] 正在ssh登录$ssh_host:$ssh_port 执行指令"
log_info "登录$ssh_host:$ssh_port 执行命令内容$cmd"
ssh_result=$(my_ssh)
exitflag=`echo $ssh_result | grep -c $cmd_keyword`
etime=`date +"%Y-%m-%d %H:%M:%S"`
if [ $exitflag == 1 ];then
echo -e "[$etime]$ssh_host \\033[32m指令发送完毕\\033[0m!"
log_info "登录$ssh_host:$ssh_port 执行命令内容$cmd 成功!"
log_info "—————————-$ssh_host 执行成功快照 ———————————————"
log_info "$ssh_result"
log_info "—————————- $ssh_host 快照结束————————————————-"
else
echo -e "[$etime]$ssh_host \\033[31m指令发送失败\\033[0m!"
log_info "登录$ssh_host:$ssh_port 执行命令内容$cmd 失败!"
log_info "————————————-$ssh_host 执行失败快照————————————————–"
log_info " $ssh_result"
log_info "————————————-$ssh_host 快照结束————————————————–"
exit 1
fi
start_auto_cap_4.1.sh
#/bin/bash/
CAP_SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
if [ ! -e $CAP_SCRIPT_DIR/config/auto_cap.config ];then
echo "Config file not exit ,Please Check ! Script Exit ! "
exit 1
fi
source $CAP_SCRIPT_DIR/config/auto_cap.config
function log_info()
{
echo "$(date +"%F %T") [INFO] $*" >> $log_file
}
ipaddrs=`echo ${hosts_list[*]}`
start_cap()
{
for ip in $ipaddrs;
do
{ ./auto_ssh_4.1.sh $ip 1
if [ $? -eq 0 ];then
ip_send_sucess[${#ip_send_sucess[@]}]=$ip
echo -e "\\033[32m $ip is in Tcpdump… \\033[0m"
else
ip_send_failed[${#ip_send_failed[@]}]=$ip
echo -e "\\033[31m $ip Tcpdump is not start,Please Check Config!\\033[0m"
fi
echo -e "———————————————————————–\\n"
}
done
}
stop_cap()
{
for ip in $ipaddrs;
do
{ ./auto_ssh_4.1.sh $ip 0
if [ $? -eq 0 ];then
ip_stop_sucess[${#ip_stop_sucess[@]}]=$ip
echo -e "\\033[31m $ip Tcpdump is stoped… \\033[0m"
else
ip_stop_failed[${#ip_stop_failed[@]}]=$ip
echo -e "\\033[31m $ip Tcpdump is not stoped,Please Check\\033[0m"
fi
echo -e "———————————————————————–\\n"
}
done
}
clear_cap()
{
for ip in $ipaddrs;
do
{ ./auto_ssh_4.1.sh $ip 2
if [ $? -eq 0 ];then
ip_clear_sucess[${#ip_clear_sucess[@]}]=$ip
echo -e "\\033[31m $ip cap_files are Deleted… \\033[0m"
else
ip_clear_failed[${#ip_clear_failed[@]}]=$ip
echo -e "\\033[31m $ip Cap_files are not Deleted,Please Check\\033[0m"
fi
echo -e "———————————————————————–\\n"
}
done
}
if [ ! -d $log_dir ];then
mkdir $log_dir
fi
log_info "开始远程登录执行命令>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
if [ "$1" = "clear" ];then
clear_cap
echo -e "\\033[31m 清理失败的主机数量:\\033[0m${#ip_clear_failed[@]}"
echo -e "\\033[31m 清理失败的主机地址:\\033[0m${ip_clear_failed[@]} \\n"
echo -e "\\033[32m 完成清理主机数量:\\033[0m${#ip_clear_sucess[@]}"
echo -e "\\033[32m 完成清理主机地址:\\033[0m${ip_clear_sucess[@]}\\n"
echo -e "\\033[36m 抓包文件清理完毕!\\033[0m"
elif [ $# -eq 0 ];then
start_cap
echo -e "\\033[31m 抓包启动失败的主机数量:\\033[0m${#ip_send_failed[@]}"
echo -e "\\033[31m 抓包启动失败的主机地址:\\033[0m${ip_send_failed[@]} \\n"
echo -e "\\033[32m 正在抓包中的主机数量:\\033[0m${#ip_send_sucess[@]}"
echo -e "\\033[32m 正在抓包中的主机地址:\\033[0m${ip_send_sucess[@]} \\n"
echo -e "\\033[36m Please Press any key to Stop the Capture: \\033[0m"
read -p " Waiting For Press to stop
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" Arg
stop_cap
echo -e "\\033[31m 抓包关闭失败的主机数量:\\033[0m${#ip_stop_failed[@]}"
echo -e "\\033[31m 抓包关闭失败的主机地址:\\033[0m${ip_stop_failed[@]} \\n"
echo -e "\\033[32m 抓包关闭成功的主机数量:\\033[0m${#ip_stop_sucess[@]}"
echo -e "\\033[32m 抓包关闭成功的主机地址:\\033[0m${ip_stop_sucess[@]} \\n"
else
echo -e " \\"$@\\" \\033[31m are illegal ,Please Check your args!\\033[0m"
fi
log_info "远程登录执行结束>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
echo -e "\\033[36m Auto Tcpdump Completed \\033[0m"
脚本的使用
1.环境准备
针对不同操作系统 在v4.1 压缩包内有Dependency_Packages目录,可以根据服务器架构和操作系统类型rpm –ivh 对应rpm包名 方式安装,先安装tcl 再安装expect,此处以Centos7为例
unzip auto_ssh_cap_v4.1.tar
2.解压后获得如下脚本和目录,配置文件在config/auto_cap.config中
vim config/auto_cap.config
配置说明
#ssh用户名
user=hadmin
#ssh登录密码
passwd=Gongan@2023
#ssh登录端口
port=22
#目标主机root用户密码
(如果ssh用户使用的root 这里填写与ssh用户密码一致)
root_passwd=Gongan@2023
#目标主机地址段配置
hosts_list=(172.168.19.{8..9} 192.168.19.{10..11})
需要登录到后台抓包的服务器地址段信息,示例为172.168.19.8-172.168.19.9, 192.168.19.10-192.168.19.11可根据现场实际网段修改,如果涉及跨网段,在括号内添加一个新的网段字符 用空格隔开
#远程主机抓包存储目录
cap_data_dir="/cloud/cap_data"
默认为该目录可按需修改
#抓包过滤条件(tcpdump过滤器语法一致)
cap_fliter=""
默认为无过滤条件,抓包会比较大 建议添加过滤规则如:
一些示例:
cap_fliter=" src host 192.168.1.1 or udp "
cap_fliter=" portrange 11000-13000 "
cap_fliter=" dst host 192.168.1.1 "
cap_fliter=" src net 192.168.1 "
#抓包过滤网卡(默认any,可根据网卡名称修改) 需要
cap_network_card="any"
默认网卡为any ,可根据需要指定网卡名称
#抓包自循时间(秒)
cycle_time=1200
默认以1200s为周期循环,覆盖抓包文件
#抓包切片大小(MB)
cut_size=300
默认为300MB进行切割抓包文件
#脚本日志存储路径(默认为脚本路径下的log目录)
log_dir="./log"
log_file="./log/auto_cmd_$(date +"%F").log"
(1)正常示例:
# ./start_auto_cap_4.1.sh
远程到服务器后台后此时打印绿色提示符:
所有主机执行完成 进入等待界面:
此时可以复现问题或进行相关的操作获取报文信息后
按任意键后回车,会逐个主机停止抓包,请勿直接Ctrl+C 退出脚本
务必每次执行都进行停止抓包操作后,退出程序,否则可能会导致抓包未停止,占用远程主机系统目录空间),如意外未执行停止 可以再次运行./start_auto_cap_4.1.sh,也可关闭远程主机抓包
打印Tcpdump Completed后说明抓包正常停止,如有失败主机 需要关注是否需要手动去后台停止
抓包文件可到对应的主机文件路径中获取
(2)清理远程主机抓包文件:
./start_auto_cap_4.1.sh clear
抓包任务完成后 ,再次执行脚本 携带clear参数 可以触发逐个主机删除cap_data目录下的pcap文件,完成后显示抓包文件清理完毕
(3)异常示例
如有远程主机无法连接,或执行的抓包命令有异常,需检查配置信息,会返回如下 提示:
(4)日志获取:
执行后会根据执行时间生成对应的日志,默认在脚本目录下的log中,可以查看抓包命令执行异常原因
日志中会附上对应的主机执行结果:
小结
至此,可以实现咱们项目上一些远程主机自动抓包的需求,同时代码的核心部分可以复用,如需要远程执行其他命令,或者批量进行修改某个系统配置的操作
待优化:
由于整个脚本使用串行的方式完成,一台远程主机 需要消耗3s左右的时间,当主机数量有几十甚至上百台的时候 会导致脚本执行效率下降,等待时间比较长,后续考虑并发方式进行优化
附件脚本下载
链接: https://pan.baidu.com/s/1r7-CcTNaXo0hjCGRgDU_gA 提取码: p416
评论前必须登录!
注册