UDP端口可达性检测(端口扫描)工具开发
1、应用场景分析
主机X与主机Y部署在AB双网环境下,两个主机间通过UDP协议进行数据交互。应用程序发送数据时,优先使用A网发送数据,如果A网异常则通过B网发送数据。两个主机应用间没有设置心跳帧 ,所以无法检测到对方UDP端口是否可达。
场景诉求
主机间发送UDP数据前,应能检测对方端口是否可达。如果不可达,则进行A/B网切换。达到动态检测对方网络服务端口状态,实时切换网络链路效果。
①、 主机不可达
场景分析:
- 主机关机。
- 防火墙开启策略。
- 网卡故障、网线连接异常。
- 网络配置错误。
场景应对策略:
通过PING echo
判断主机是否可达。
②、 端口不可达
主机可达,端口不可达。
场景分析:
- 服务未启动,端口未监听。
- 防火墙开启策略。
场景应对策略:
结合ICMP和内核网络协议栈,判断UDP端口是否可达。
2、UDP端口扫描程序开发
①、Windows程序
【1】、 理论知识
略。
【2】、 C++源码
界面控件设计。
对话框初始化部分代码。
BOOL CUdpDetectDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// 将“关于...”菜单项添加到系统菜单中。
// IDM_ABOUTBOX 必须在系统命令范围内。
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
BOOL bNameValid;
CString strAboutMenu;
bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
ASSERT(bNameValid);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// 设置此对话框的图标。 当应用程序主窗口不是对话框时,框架将自动
// 执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标
// 初始化Windows套接字
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
CString strMsg;
strMsg.Format("WSAStartup失败!");
return 0;
}
// CListCtrl
DWORD dwStyle = m_ListCtrl.GetExtendedStyle();
m_ListCtrl.SetExtendedStyle(dwStyle | LVS_EX_FULLROWSELECT );
m_ListCtrl.InsertColumn(0, _T("主机端口"), LVCFMT_LEFT, 130);
m_ListCtrl.InsertColumn(1, _T("扫描结果"), LVCFMT_LEFT, 100);
m_ListCtrl.InsertColumn(2, _T("ICMP反馈"), LVCFMT_LEFT, 350);
// 默认参数;
m_EditScanHostIP.SetWindowText("192.168.58.1");
m_EditScanPortStartNo.SetWindowText("50000");
m_EditScanEndPortNo.SetWindowText("50005");
return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}
端口扫描部分代码。
//扫描端口;
void CUdpDetectDlg::ScanPort()
{
CString strScanHost;
m_EditScanHostIP.GetWindowText(strScanHost);
CString strScanStartPort;
m_EditScanPortStartNo.GetWindowText(strScanStartPort);
CString strScanEndPort;
m_EditScanEndPortNo.GetWindowText(strScanEndPort);
int iStartPort = _ttoi(strScanStartPort);
int iEndPort = _ttoi(strScanEndPort);
if (iEndPort < iStartPort) return;
m_ListCtrl.DeleteAllItems();
for (int ii = iStartPort; ii <= iEndPort;++ii)
{
BOOL isPortOpenF = FALSE;
char szErrText[256] = { 0 };
sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sock < 0) return;
//设置收发缓冲区大小;
int iTemp = 1024 * 2;
setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)&iTemp, sizeof(iTemp));
setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)&iTemp, sizeof(iTemp));
//设置接收超时;
int iRecvTimeout = 1000;
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&iRecvTimeout, sizeof(iRecvTimeout));
struct sockaddr_in txaddr;
txaddr.sin_family = AF_INET;
txaddr.sin_addr.s_addr = inet_addr(strScanHost.GetBuffer(0));
txaddr.sin_port = htons(ii);
int addrlen = sizeof(txaddr);
char txbuff[] = "";
int txlen = sendto(sock, txbuff, sizeof(txbuff), 0, (sockaddr*)&txaddr, addrlen);
char rxbuff[24] = { 0 };
int rxlen = recvfrom(sock, rxbuff, 24, 0, (sockaddr*)&txaddr, &addrlen);
if (rxlen < 0)
{
if (WSAGetLastError() == WSAECONNRESET)
{
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, WSAECONNRESET, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),szErrText, 256, NULL);
isPortOpenF = FALSE;
}
else
isPortOpenF = TRUE;
}
CString strHostDesc;
strHostDesc.Format("%s:%d", strScanHost, ii);
CString strPortState;
if (isPortOpenF)
strPortState.Format("端口可达");
else
strPortState.Format("端口不可达");
int iIndex = m_ListCtrl.GetItemCount();
m_ListCtrl.InsertItem(iIndex, strHostDesc);
m_ListCtrl.SetItemText(iIndex, 1, strPortState);
m_ListCtrl.SetItemText(iIndex, 2, CString(szErrText));
closesocket(sock);
}
}
【3】、 编译程序
【4】、 运行程序
先将目标主机的50001
端口开启监听。然后用测试程序检测。
开始扫描端口。
②、 Linux程序
【1】、 理论知识
略。
【2】、 C++源码
UdpScan.cxx
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string>
#include<algorithm>
int main()
{
printf("*********************************************************************\n");
printf("* *\n");
printf("* Linux 端口扫描工具 V0.1 *\n");
printf("* *\n");
printf("*********************************************************************\n");
printf("请输入扫描主机IP地址: ");
char input[256] = {0};
char* ptr = fgets(input,256,stdin);
if(ptr == nullptr) { printf("输入参数异常,结束程序!\n"); }
std::string strHost = std::string(ptr);
printf("请输入扫描UDP端口范围(比如2000-2500): ");
ptr = fgets(input,256,stdin);
if(ptr == nullptr) { printf("输入参数异常,结束程序!\n"); }
std::string strPort = std::string(ptr);
int iStartPort = 0, iEndPort = 0;
int pos = strPort.find("-");
if(pos != std::string::npos)
{
std::string strStartPort = strPort.substr(0,pos);
std::string strEndPort = strPort.substr(pos+1);
iStartPort = std::stoi(strStartPort);
iEndPort = std::stoi(strEndPort);
}
if(iEndPort < iStartPort) return 1;
for(int ii = iStartPort; ii <= iEndPort; ++ ii)
{
int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
//将ICMP端口不可达信息报到应用层协议
int flag = 1;
setsockopt(sock, IPPROTO_IP, IP_RECVERR , &flag,sizeof(int));
//设置接收超时
struct timeval timeout = {0,300};
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(struct timeval));
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(strHost.c_str());
addr.sin_port = htons(ii);
char txbuff[] = "";
int ret = sendto(sock,txbuff,sizeof(txbuff),0,(struct sockaddr*)&addr,sizeof(addr));
char rxbuff[24] = { 0 };
int addrlen = sizeof(addr);
int rxlen = recvfrom(sock, rxbuff, sizeof(rxbuff), 0, (struct sockaddr *)&addr, &addrlen);
if (rxlen == -1)
{
//剔除回车符
strHost.erase(std::remove(strHost.begin(), strHost.end(), '\n'), strHost.end());
if (errno == ECONNREFUSED)
{
printf("主机【%s:%d】端口不可达!\n",strHost.c_str(),ii);
}
else
printf("主机【%s:%d】端口可达!\n",strHost.c_str(),ii);
}
close(sock);
}
printf("端口扫描结束!\n");
return 0;
}
代码截图:
【3】、 编译程序
g++ -o udpscan UdpScan.cxx -fpermissive -std=c++11 -w -W
【4】、 运行程序
先将目标主机的50001
端口开启监听。然后用测试程序检测。
开始扫描端口。