计算机网络实验报告完整版

计算机网络实验报告

实验一 wireshark 抓包工具使用

一、实验目的

  • 学习wireshark抓包工具的使用。
  • 了解wireshark抓包工具的功能,明确抓包软件的功能。
  • 通过学习,进一步理解协议及网络体系结构思想。

二、实验原理

Wireshark 是网络包分析工具。网络包分析工具的主要作用是尝试捕获网络包,并尝试显示包的尽可能详细的情况。

主要应用:

  • 网络管理员用来解决网络问题
  • 网络安全工程师用来检测安全隐患
  • 开发人员用来测试协议执行情况
  • 用来学习网络协议

三、实验内容

下载 WIRESHARK,学习工具的使用和功能。

四、实验过程

1、wireshark 的下载、安装与配置

访问 wireshark 官网:https://www.wireshark.org/

下载并安装 Windows Installer (64-bit) 版本。

2、初识 wireshark

Wireshark 的工作原理是捕获某一网卡的数据包,当配置多块网卡时,需要选择一块网卡进行抓包,此处选择 WLAN,然后双击即可。

3、查看网络环境

控制面板-网络和Internet-网络和共享中心-更改适配器设置-WLAN-状态-详细信息,得到了以下关键信息:

名称 地址
IPv4 地址 10.19.128.57
子网掩码 255.255.248.0
默认网关 10.19.135.254

4、启动抓包

截图显示的是 wireshark 抓到的部分数据包,有 TCP、UDP、ARP、MDNS、ICMPv6 等多种协议的数据包。

名称 对应层次
Frame 物理层
Ethernet Ⅱ 数据链路层
Internet Protocol Version 4 网络层
Transmission Control Protocol 传输层
Hypertext Transfer Protocol 应用层

5、追踪打开一个网页时,TCP 的三次握手

以访问百度为例,首先,打开浏览器的一个无痕窗口,并确保电脑上的代理处于关闭状态,然后过滤出 DNS 我们可以发现解析百度的包,找到解析后的真实 ip,

然后设置过滤规则:

ip.src == 182.61.200.7 or ip.dst == 182.61.200.7

将过滤出来的包按时间排序,开头的三个包是TCP 的三次握手,

  • 第一次握手:客户机(10.19.128.57)主动打开一条TCP连接,从客户机(10.19.128.57)发送报文段至服务器(182.61.200.7):SYN=1,Seq=0,不携带数据,等待服务器确认。
  • 第二次握手:服务器(182.61.200.7)返回确认报文至客户机(10.0.0.108):SYN=1,ACK=1,Seq=0,不携带数据。
  • 第三次握手:从客户机(10.19.128.57)发送报文段至服务器(182.61.200.7):ACK=1,Seq=1,不携带数据,TCP连接正式建立。

下图为第二次握手的报文段的详细分析:

实验二 winpcap 编程

实验目的

  • 了解 winpcap 的架构
  • 学习 winpcap 编程

实验原理

WinPcap是一个基于Win32平台的,用于捕获网络数据包并进行分析的开源库。

大多数网络应用程序通过被广泛使用的操作系统元件来访问网络,比如sockets。这是一种简单的实现方式,因为操作系统已经妥善处理了底层具体实现细节(比如协议处理,封装数据包等等),并且提供了一个与读写文件类似的,令人熟悉的接口。

然而,有些时候,这种“简单的方式”并不能满足任务的需求,因为有些应用程序需要直接访问网络中的数据包。也就是说,那些应用程序需要访问原始数据包,即没有被操作系统利用网络协议处理过的数据包。

WinPcap产生的目的,就是为Win32应用程序提供这种访问方式;WinPcap提供了以下功能:

  1. 捕获原始数据包,无论它是发往某台机器的,还是在其他设备(共享媒介)上进行交换的
  2. 在数据包发送给某应用程序前,根据用户指定的规则过滤数据包
  3. 将原始数据包通过网络发送出去
  4. 收集并统计网络流量信息

以上这些功能需要借助安装在Win32内核中的网络设备驱动程序才能实现,再加上几个动态链接库DLL。

所有这些功能都能通过一个强大的编程接口来表现出来,易于开发,并能在不同的操作系统上使用。

WinPcap可以被用来制作网络分析、监控工具。一些基于WinPcap的典型应用有:

  1. 网络与协议分析器 (network and protocol analyzers)
  2. 网络监视器 (network monitors)
  3. 网络流量记录器 (traffic loggers)
  4. 网络流量发生器 (traffic generators)
  5. 用户级网桥及路由 (user-level bridges and routers)
  6. 网络入侵检测系统 (network intrusion detection systems (NIDS))
  7. 网络扫描器 (network scanners)
  8. 安全工具 (security tools)

实验内容

通过学习WINPCAP架构,编写一个网络抓包程序。

实验过程

首先要说明一点,winpcap 官网建议使用 npcap,因为根据官网说明,winpcap 已经不适用于 windows10 和 windows11,有些函数可能会出现意想不到的效果。根据我的实践,发现winpcap无法获取适配器的具体名称,比如我的 wifi6 适配器,使用 winpcap 就只能获取模糊的 "Microsoft" 这个名称,而使用 npcap 就可以获取其完整的名称,而且 winpcap 或识别不了一些适配器,而这一点在 npcap 上得到了很好的改善。

1、npcap 的下载安装与配置

  • 访问npcap官网:https://www.winpcap.org/
  • 选择npcap的最新版,下载Installer for Windows并安装
  • 在VS中导入相应头文件和lib文件

安装配置的详细过程见我的这一篇博客,这里不再赘述。

2、获取设备列表

通常情况下,一个基于Npcap的应用程序所做的第一件事就是获得一个连接的网络适配器的列表。libpcap和Npcap都为这个目的提供了cap_findalldevs_ex()函数:这个函数返回一个cap_if结构的链接列表,每个结构都包含一个连接的适配器的全面信息。特别是,字段namedescription分别包含了相应设备的名称和可读的描述。

下面的代码检索适配器列表并显示在屏幕上,如果没有找到适配器,则打印一个错误。

#ifdef _MSC_VER
/*
 * we do not want the warnings about the old deprecated and unsecure CRT functions
 * since these examples can be compiled under *nix as well
 */
#define _CRT_SECURE_NO_WARNINGS
#endif

#include "pcap.h"

main()
{
    pcap_if_t* alldevs;
    pcap_if_t* d;
    int i = 0;
    char errbuf[PCAP_ERRBUF_SIZE];

    /* Retrieve the device list from the local machine */
    if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING,
        NULL /* auth is not needed */,
        &alldevs, errbuf) == -1)
    {
        fprintf(stderr,
            "Error in pcap_findalldevs_ex: %s\n",
            errbuf);
        exit(1);
    }

    /* Print the list */
    for (d = alldevs; d != NULL; d = d->next)
    {
        printf("%d. %s", ++i, d->name);
        if (d->description)
            printf(" (%s)\n", d->description);
        else
            printf(" (No description available)\n");
    }

    if (i == 0)
    {
        printf("\nNo interfaces found! Make sure Npcap is installed.\n");
        return;
    }

    /* We don't need any more the device list. Free it */
    pcap_freealldevs(alldevs);
}

运行结果:

下面利用控制面板查看到的网络适配器:

:可以发现,通过程序打印出来的适配器列表和通过 windows 的设备管理器查看到的适配器列表有一些区别,主要是数量上的区别。已经打印出来的部分都可以和设备管理器中的对应得上。

关于这个代码的一些注释如下。

首先,pcap_findalldevs_ex()和其他libpcap函数一样,有一个errbuf参数。这个参数指向一个由libpcap填充的字符串,其中包括出错时的错误描述。

其次,请记住,并不是所有的操作系统都由libpcap支持,提供网络接口的描述,因此,如果我们想写一个可移植的应用程序,我们必须考虑描述为空的情况:在这种情况下,我们打印字符串"No description available"。

最后注意,当我们用完这个列表后,我们用pcap_freealldevs()释放它。

假设我们已经编译了该程序,让我们试着运行它。在我的 Windows10 系统上,我们得到的结果是

1. rpcap://\Device\NPF_{3CEB626A-F27B-4267-B79D-2C6280763C13} (Network adapter 'WAN Miniport (Network Monitor)' on local host)
2. rpcap://\Device\NPF_{4BC226A2-E243-421C-9438-E30A96261403} (Network adapter 'WAN Miniport (IPv6)' on local host)
3. rpcap://\Device\NPF_{D3594FDB-28EF-4133-931A-1CDF0C2F56E1} (Network adapter 'WAN Miniport (IP)' on local host)
4. rpcap://\Device\NPF_{9464D53B-3103-4A4A-9B91-75A5DA984497} (Network adapter 'Microsoft Wi-Fi Direct Virtual Adapter' on local host)
5. rpcap://\Device\NPF_{0D437C70-8383-45EE-A70B-D00C04950AEF} (Network adapter 'Intel(R) Wi-Fi 6 AX200 160MHz' on local host)
6. rpcap://\Device\NPF_{6A1116E4-93E7-4A84-99D1-7E4CB0428CCE} (Network adapter 'Microsoft Wi-Fi Direct Virtual Adapter #2' on local host)
7. rpcap://\Device\NPF_Loopback (Network adapter 'Adapter for loopback traffic capture' on local host)
8. rpcap://\Device\NPF_{B5950B27-9860-4CEE-ADF9-3AFF86481291} (Network adapter 'Realtek PCIe GbE Family Controller' on local host)

3、获取已安装设备的高级信息

上一部分(名为 "获取设备列表 "的部分)演示了如何获取关于可用适配器的基本信息(即设备名称和描述)。实际上,Npcap还提供了其他高级信息。特别是,由pcap_findalldevs_ex()返回的每个cap_if结构都包含一个cap_addr结构的列表,其中包括:

  • 该接口的地址列表。
  • 一个网络掩码的列表(每个掩码对应于地址列表中的一个条目)。
  • 广播地址的列表(每个地址对应于地址列表中的一个条目)。
  • 目标地址的列表(每个地址对应于地址列表中的一个条目)。

此外,cap_findalldevs_ex()还可以返回远程适配器和位于指定本地文件夹中的pcap文件列表。

下面的代码提供了一个 ifprint() 函数,它可以打印 pcap_if 结构的全部内容。程序对 pcap_findalldevs_ex() 返回的每个条目都会调用该函数。

/* Print all the available information on the given interface */
void ifprint(pcap_if_t *d)
{
  pcap_addr_t *a;
  char ip6str[128];

  /* Name */
  printf("%s\n",d->name);

  /* Description */
  if (d->description)
    printf("\tDescription: %s\n",d->description);

  /* Loopback Address*/
  printf("\tLoopback: %s\n",(d->flags & PCAP_IF_LOOPBACK)?"yes":"no");

  /* IP addresses */
  for(a=d->addresses;a;a=a->next) {
    printf("\tAddress Family: #%d\n",a->addr->sa_family);
  
    switch(a->addr->sa_family)
    {
      case AF_INET:
        printf("\tAddress Family Name: AF_INET\n");
        if (a->addr)
          printf("\tAddress: %s\n",iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr));
        if (a->netmask)
          printf("\tNetmask: %s\n",iptos(((struct sockaddr_in *)a->netmask)->sin_addr.s_addr));
        if (a->broadaddr)
          printf("\tBroadcast Address: %s\n",iptos(((struct sockaddr_in *)a->broadaddr)->sin_addr.s_addr));
        if (a->dstaddr)
          printf("\tDestination Address: %s\n",iptos(((struct sockaddr_in *)a->dstaddr)->sin_addr.s_addr));
        break;

      case AF_INET6:
        printf("\tAddress Family Name: AF_INET6\n");
        if (a->addr)
          printf("\tAddress: %s\n", ip6tos(a->addr, ip6str, sizeof(ip6str)));
       break;

      default:
        printf("\tAddress Family Name: Unknown\n");
        break;
    }
  }
  printf("\n");
}

完整代码:

#ifdef _MSC_VER
#define _CRT_SECURE_NO_WARNINGS
#endif

#include <pcap.h>
#include <stdio.h>

#ifndef _WIN32
#include <sys/socket.h>
#include <netinet/in.h>
#else
#include <winsock.h>
#endif

#ifdef _WIN32
#include <tchar.h>
BOOL LoadNpcapDlls()
{
	_TCHAR npcap_dir[512];
	UINT len;
	len = GetSystemDirectory(npcap_dir, 480);
	if (!len) {
		fprintf(stderr, "Error in GetSystemDirectory: %x", GetLastError());
		return FALSE;
	}
	_tcscat_s(npcap_dir, 512, _T("\\Npcap"));
	if (SetDllDirectory(npcap_dir) == 0) {
		fprintf(stderr, "Error in SetDllDirectory: %x", GetLastError());
		return FALSE;
	}
	return TRUE;
}
#endif


// Function prototypes
void ifprint(pcap_if_t* d);
char* iptos(u_long in);
char* ip6tos(struct sockaddr* sockaddr, char* address, int addrlen);


int main()
{
	pcap_if_t* alldevs;
	pcap_if_t* d;
	char errbuf[PCAP_ERRBUF_SIZE + 1];

#ifdef _WIN32
	/* Load Npcap and its functions. */
	if (!LoadNpcapDlls())
	{
		fprintf(stderr, "Couldn't load Npcap\n");
		exit(1);
	}
#endif

	/* Retrieve the device list */
	if (pcap_findalldevs(&alldevs, errbuf) == -1)
	{
		fprintf(stderr, "Error in pcap_findalldevs: %s\n", errbuf);
		exit(1);
	}

	/* Scan the list printing every entry */
	for (d = alldevs;d;d = d->next)
	{
		ifprint(d);
	}

	/* Free the device list */
	pcap_freealldevs(alldevs);

	return 0;
}



/* Print all the available information on the given interface */
void ifprint(pcap_if_t* d)
{
	pcap_addr_t* a;
	char ip6str[128];

	/* Name */
	printf("%s\n", d->name);

	/* Description */
	if (d->description)
		printf("\tDescription: %s\n", d->description);

	/* Loopback Address*/
	printf("\tLoopback: %s\n", (d->flags & PCAP_IF_LOOPBACK) ? "yes" : "no");

	/* IP addresses */
	for (a = d->addresses;a;a = a->next) {
		printf("\tAddress Family: #%d\n", a->addr->sa_family);

		switch (a->addr->sa_family)
		{
		case AF_INET:
			printf("\tAddress Family Name: AF_INET\n");
			if (a->addr)
				printf("\tAddress: %s\n", iptos(((struct sockaddr_in*)a->addr)->sin_addr.s_addr));
			if (a->netmask)
				printf("\tNetmask: %s\n", iptos(((struct sockaddr_in*)a->netmask)->sin_addr.s_addr));
			if (a->broadaddr)
				printf("\tBroadcast Address: %s\n", iptos(((struct sockaddr_in*)a->broadaddr)->sin_addr.s_addr));
			if (a->dstaddr)
				printf("\tDestination Address: %s\n", iptos(((struct sockaddr_in*)a->dstaddr)->sin_addr.s_addr));
			break;

		case AF_INET6:
			printf("\tAddress Family Name: AF_INET6\n");
#ifndef __MINGW32__ /* Cygnus doesn't have IPv6 */
			if (a->addr)
				printf("\tAddress: %s\n", ip6tos(a->addr, ip6str, sizeof(ip6str)));
#endif
			break;

		default:
			printf("\tAddress Family Name: Unknown\n");
			break;
		}
	}
	printf("\n");
}

/* From tcptraceroute, convert a numeric IP address to a string */
#define IPTOSBUFFERS	12
char* iptos(u_long in)
{
	static char output[IPTOSBUFFERS][3 * 4 + 3 + 1];
	static short which;
	u_char* p;

	p = (u_char*)&in;
	which = (which + 1 == IPTOSBUFFERS ? 0 : which + 1);
	sprintf(output[which], "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
	return output[which];
}

#ifndef __MINGW32__ /* Cygnus doesn't have IPv6 */
char* ip6tos(struct sockaddr* sockaddr, char* address, int addrlen)
{
	socklen_t sockaddrlen;

#ifdef _WIN32
	sockaddrlen = sizeof(struct sockaddr_in6);
#else
	sockaddrlen = sizeof(struct sockaddr_storage);
#endif


	if (getnameinfo(sockaddr,
		sockaddrlen,
		address,
		addrlen,
		NULL,
		0,
		NI_NUMERICHOST) != 0) address = NULL;

	return address;
}
#endif /* __MINGW32__ */

运行结果:

然后我们看一下这个 WIFI6 适配器,发现它的地址和通过 powershell 查看到的 ipv4 地址是一样的:

结果分析:

npcap 获取了 6 块逻辑网卡的高级信息:包含设备描述、IP地址、子网掩码、广播地址等。

4、打开适配器并捕获数据包

现在我们已经看到了如何获得一个适配器来玩(对,play,所谓的 toy programs),让我们开始真正的工作,打开一个适配器并捕获一些流量。在这一课中,我们将编写一个程序,打印出流经适配器的每个数据包的一些信息。

打开一个捕获设备的函数是 pcap_open()。参数 snaplenflagsto_ms 值得解释一下。

snaplen:指定了要捕获的数据包的部分。在一些操作系统上(如xBSD和Win32),数据包驱动可以被配置为只捕获任何数据包的初始部分:这减少了要复制到应用程序的数据量,因此提高了捕获的效率。在这种情况下,我们使用65536这个值,这比我们可能遇到的最大MTU要高。通过这种方式,我们确保应用程序将始终收到整个数据包。

flags:最重要的标志是指示适配器是否将进入混杂模式的标志。在正常操作中,适配器仅捕获来自网络的发往它的数据包;因此,其他主机交换的数据包将被忽略。相反,当适配器处于混杂模式时,它会捕获所有数据包,无论它们是否发往它。这意味着在共享媒体(如非交换以太网)上,Npcap 将能够捕获其他主机的数据包。混杂模式是大多数捕获应用程序的默认模式,因此我们在以下代码中启用它。

to_ms:指定读取超时,以毫秒为单位。适配器上的读取(例如,使用 pcap_dispatch()pcap_next_ex())将始终在 to_ms 毫秒后返回,即使网络上没有可用的数据包。如果适配器处于统计模式,to_ms 还定义统计报告之间的间隔(有关统计模式的信息,请参阅官网教程 "wpcap_tut9")。将 to_ms 设置为 0 意味着没有超时,如果没有数据包到达,适配器上的读取永远不会返回。另一端的 -1 超时会导致适配器上的读取始终立即返回。

#ifdef _MSC_VER
/*
 * we do not want the warnings about the old deprecated and unsecure CRT functions
 * since these examples can be compiled under *nix as well
 */
#define _CRT_SECURE_NO_WARNINGS
#endif

#include <pcap.h>
#include <stdio.h>
#include <time.h>
#ifdef _WIN32
#include <tchar.h>
BOOL LoadNpcapDlls()
{
	_TCHAR npcap_dir[512];
	UINT len;
	len = GetSystemDirectory(npcap_dir, 480);
	if (!len) {
		fprintf(stderr, "Error in GetSystemDirectory: %x", GetLastError());
		return FALSE;
	}
	_tcscat_s(npcap_dir, 512, _T("\\Npcap"));
	if (SetDllDirectory(npcap_dir) == 0) {
		fprintf(stderr, "Error in SetDllDirectory: %x", GetLastError());
		return FALSE;
	}
	return TRUE;
}
#endif

/* prototype of the packet handler */
void packet_handler(u_char* param, const struct pcap_pkthdr* header, const u_char* pkt_data);

int main()
{
	pcap_if_t* alldevs;
	pcap_if_t* d;
	int inum;
	int i = 0;
	pcap_t* adhandle;
	char errbuf[PCAP_ERRBUF_SIZE];

#ifdef _WIN32
	/* Load Npcap and its functions. */
	if (!LoadNpcapDlls())
	{
		fprintf(stderr, "Couldn't load Npcap\n");
		exit(1);
	}
#endif

	/* Retrieve the device list */
	if (pcap_findalldevs(&alldevs, errbuf) == -1)
	{
		fprintf(stderr, "Error in pcap_findalldevs: %s\n", errbuf);
		exit(1);
	}

	/* Print the list */
	for (d = alldevs; d; d = d->next)
	{
		printf("%d. %s", ++i, d->name);
		if (d->description)
			printf(" (%s)\n", d->description);
		else
			printf(" (No description available)\n");
	}

	if (i == 0)
	{
		printf("\nNo interfaces found! Make sure Npcap is installed.\n");
		return -1;
	}

	printf("Enter the interface number (1-%d):", i);
	scanf("%d", &inum);

	if (inum < 1 || inum > i)
	{
		printf("\nInterface number out of range.\n");
		/* Free the device list */
		pcap_freealldevs(alldevs);
		return -1;
	}

	/* Jump to the selected adapter */
	for (d = alldevs, i = 0; i < inum - 1;d = d->next, i++);

	/* Open the device */
	/* Open the adapter */
	if ((adhandle = pcap_open_live(d->name,	// name of the device
		65536,			// portion of the packet to capture. 
					   // 65536 grants that the whole packet will be captured on all the MACs.
		1,				// promiscuous mode (nonzero means promiscuous)
		1000,			// read timeout
		errbuf			// error buffer
	)) == NULL)
	{
		fprintf(stderr, "\nUnable to open the adapter. %s is not supported by Npcap\n", d->name);
		/* Free the device list */
		pcap_freealldevs(alldevs);
		return -1;
	}

	printf("\nlistening on %s...\n", d->description);

	/* At this point, we don't need any more the device list. Free it */
	pcap_freealldevs(alldevs);

	/* start the capture */
	pcap_loop(adhandle, 0, packet_handler, NULL);

	pcap_close(adhandle);
	return 0;
}


/* Callback function invoked by libpcap for every incoming packet */
void packet_handler(u_char* param, const struct pcap_pkthdr* header, const u_char* pkt_data)
{
	struct tm* ltime;
	char timestr[16];
	time_t local_tv_sec;

	/*
	 * unused parameters
	 */
	(VOID)(param);
	(VOID)(pkt_data);

	/* convert the timestamp to readable format */
	local_tv_sec = header->ts.tv_sec;
	ltime = localtime(&local_tv_sec);
	strftime(timestr, sizeof timestr, "%H:%M:%S", ltime);

	printf("%s,%.6d len:%d\n", timestr, header->ts.tv_usec, header->len);

}

运行结果:

打开适配器后,可以使用 pcap_dispatch()pcap_loop() 开始捕获。这两个函数非常相似,不同之处在于 pcap_dispatch() 在超时到期时返回(尽管不能保证),而 pcap_loop() 在捕获到 cnt 个数据包之前不会返回,因此它可以在下一个任意时间段内阻塞-利用网络。 pcap_loop() 足以满足我们的目的,而 pcap_dispatch() 通常用于更复杂的程序中。

这两个函数都有一个回调参数,packet_handler,指向一个将接收数据包的函数。这个函数由 libpcap 为来自网络的每个新数据包调用,并接收一个通用状态(对应于 pcap_loop()pcap_dispatch()user 参数),一个包含关于数据包的一些信息的标头,例如时间戳和长度以及数据包的实际数据,包括所有协议头。请注意,帧 CRC 通常不存在,因为它在帧验证后被网络适配器删除。另请注意,大多数适配器会丢弃具有错误 CRC 的数据包,因此 Npcap 通常无法捕获它们。

上面的代码从 pcap_pkthdr 标头中提取每个数据包的时间戳和长度,并将它们打印在屏幕上。

不过要注意,使用 pcap_loop() 可能有一个缺点,主要与抓包驱动程序调用处理程序有关;因此,用户应用程序无法直接控制它。另一种方法(并且具有更易读的程序)是使用 pcap_next_ex() 函数。这里就不再赘述。详情可以参考官方教程

5、在没有回调的情况下捕获数据包

本节的代码的行为与前面的程序(名为"打开适配器并捕获数据包"的部分)完全一样,但它使用了 pcap_next_ex() 而不是 pcap_loop()

pcap_loop()的基于回调的捕获机制很优雅,在某些情况下它可能是一个很好的选择。然而,处理回调有时并不实用——它常常使程序更加复杂,特别是在多线程应用程序或C++类的情况下。

在这些情况下,pcap_next_ex()通过直接调用来获取数据包——使用pcap_next_ex(),数据包只有在程序员想要的时候才会被接收。

这个函数的参数与捕获回调相同。它接收一个适配器描述符和几个指针,这些指针将被初始化并返回给用户(一个指向pcap_pkthdr结构,另一个指向包含数据包的缓冲区)。

在下面的程序中,我们回收了上一节代码中的回调代码,并将其移到main()中,紧接着调用pcap_next_ex()

#ifdef _MSC_VER
/*
 * we do not want the warnings about the old deprecated and unsecure CRT functions
 * since these examples can be compiled under *nix as well
 */
#define _CRT_SECURE_NO_WARNINGS
#endif

#include <pcap.h>
#include <stdio.h>
#include <time.h>
#ifdef _WIN32
#include <tchar.h>
BOOL LoadNpcapDlls()
{
	_TCHAR npcap_dir[512];
	UINT len;
	len = GetSystemDirectory(npcap_dir, 480);
	if (!len) {
		fprintf(stderr, "Error in GetSystemDirectory: %x", GetLastError());
		return FALSE;
	}
	_tcscat_s(npcap_dir, 512, _T("\\Npcap"));
	if (SetDllDirectory(npcap_dir) == 0) {
		fprintf(stderr, "Error in SetDllDirectory: %x", GetLastError());
		return FALSE;
	}
	return TRUE;
}
#endif

int main()
{
	pcap_if_t* alldevs;
	pcap_if_t* d;
	int inum;
	int i = 0;
	pcap_t* adhandle;
	int res;
	char errbuf[PCAP_ERRBUF_SIZE];
	struct tm* ltime;
	char timestr[16];
	struct pcap_pkthdr* header;
	const u_char* pkt_data;
	time_t local_tv_sec;

#ifdef _WIN32
	/* Load Npcap and its functions. */
	if (!LoadNpcapDlls())
	{
		fprintf(stderr, "Couldn't load Npcap\n");
		exit(1);
	}
#endif

	/* Retrieve the device list */
	if (pcap_findalldevs(&alldevs, errbuf) == -1)
	{
		fprintf(stderr, "Error in pcap_findalldevs: %s\n", errbuf);
		return -1;
	}

	/* Print the list */
	for (d = alldevs; d; d = d->next)
	{
		printf("%d. %s", ++i, d->name);
		if (d->description)
			printf(" (%s)\n", d->description);
		else
			printf(" (No description available)\n");
	}

	if (i == 0)
	{
		printf("\nNo interfaces found! Make sure Npcap is installed.\n");
		return -1;
	}

	printf("Enter the interface number (1-%d):", i);
	scanf("%d", &inum);

	if (inum < 1 || inum > i)
	{
		printf("\nInterface number out of range.\n");
		/* Free the device list */
		pcap_freealldevs(alldevs);
		return -1;
	}

	/* Jump to the selected adapter */
	for (d = alldevs, i = 0; i < inum - 1;d = d->next, i++);

	/* Open the adapter */
	if ((adhandle = pcap_open_live(d->name,	// name of the device
		65536,			// portion of the packet to capture. 
					   // 65536 grants that the whole packet will be captured on all the MACs.
		1,				// promiscuous mode (nonzero means promiscuous)
		1000,			// read timeout
		errbuf			// error buffer
	)) == NULL)
	{
		fprintf(stderr, "\nUnable to open the adapter. %s is not supported by Npcap\n", d->name);
		/* Free the device list */
		pcap_freealldevs(alldevs);
		return -1;
	}

	printf("\nlistening on %s...\n", d->description);

	/* At this point, we don't need any more the device list. Free it */
	pcap_freealldevs(alldevs);

	/* Retrieve the packets */
	while ((res = pcap_next_ex(adhandle, &header, &pkt_data)) >= 0) {

		if (res == 0)
			/* Timeout elapsed */
			continue;

		/* convert the timestamp to readable format */
		local_tv_sec = header->ts.tv_sec;
		ltime = localtime(&local_tv_sec);
		strftime(timestr, sizeof timestr, "%H:%M:%S", ltime);

		printf("%s,%.6d len:%d\n", timestr, header->ts.tv_usec, header->len);
	}

	if (res == -1) {
		printf("Error reading the packets: %s\n", pcap_geterr(adhandle));
		return -1;
	}

	pcap_close(adhandle);
	return 0;
}

为什么我们使用 pcap_next_ex() 而不是以前的 pcap_next()?因为 pcap_next() 有一些缺点。首先,它的效率很低,因为它隐藏了回调方法,但仍然依赖于 pcap_dispatch()。其次,它不能检测EOF,所以当从文件中收集数据包时,它不是很有用。

还请注意,pcap_next_ex()对成功、超时、错误和EOF条件返回不同的值。

6、过滤流量

Npcap(以及 libpcap)提供的最强大的功能之一是过滤引擎。它提供了一种非常有效的方式来接收网络流量的子集,并且(通常)与 Npcap 提供的捕获机制集成。用于过滤数据包的函数是 pcap_compile()pcap_setfilter()

pcap_compile() 接受一个包含高级布尔(过滤器)表达式的字符串,并生成一个低级别的字节码,该字节码可由数据包驱动程序中的文件过滤器引擎解释。布尔表达式的语法可以在本文档的过滤表达式语法部分中找到。

pcap_setfilter()在内核驱动中把一个过滤器与一个捕获会话联系起来。一旦调用 pcap_setfilter(),相关的过滤器将被应用于所有来自网络的数据包,所有符合要求的数据包(即布尔表达式评估为真的数据包)将被实际拷贝到应用程序中。

下面的代码展示了如何编译和设置一个过滤器。注意,我们必须从描述适配器的 pcap_if 结构中获取网络掩码,因为由 pcap_compile() 创建的一些过滤器需要它。

在这个代码片段中传递给 pcap_compile() 的过滤器是“ip and tcp”,这意味着“只保留 IPv4 和 TCP 的数据包并将它们传递给应用程序”。

if (d->addresses != NULL)
  /* Retrieve the mask of the first address of the interface */
  netmask=((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr;
else
  /* If the interface is without an address
   * we suppose to be in a C class network */
  netmask=0xffffff; 


//compile the filter
if (pcap_compile(adhandle, &fcode, "ip and tcp", 1, netmask) < 0)
{
  fprintf(stderr,
    "\nUnable to compile the packet filter. Check the syntax.\n");
  /* Free the device list */
  pcap_freealldevs(alldevs);
  return -1;
}

//set the filter
if (pcap_setfilter(adhandle, &fcode) < 0)
{
  fprintf(stderr,"\nError setting the filter.\n");
  /* Free the device list */
  pcap_freealldevs(alldevs);
  return -1;
}

上面的代码将会在下面的“解读数据包”这一节种使用到。

7、解读数据包

现在我们能够捕获和过滤网络流量,我们希望将我们的知识用于一个简单的“现实世界”应用程序。

在本节中,我们将从之前的小节中获取代码,并使用这些部分来构建一个更有用的程序。当前程序的主要目的是展示如何解析和解释捕获的数据包的协议头。由此生成的应用程序,称为 UDPdump,打印我们网络上 UDP 流量的摘要。

我们选择了解析和显示UDP协议,因为它比其他协议(如TCP)更容易获得,因此是一个很好的初始例子。让我们看一下代码。

#include <pcap.h>
#include <Winsock2.h>
#include <time.h>
#include <tchar.h>
BOOL LoadNpcapDlls()
{
    _TCHAR npcap_dir[512];
    UINT len;
    len = GetSystemDirectory(npcap_dir, 480);
    if (!len) {
        fprintf(stderr, "Error in GetSystemDirectory: %x", GetLastError());
        return FALSE;
    }
    _tcscat_s(npcap_dir, 512, _T("\\Npcap"));
    if (SetDllDirectory(npcap_dir) == 0) {
        fprintf(stderr, "Error in SetDllDirectory: %x", GetLastError());
        return FALSE;
    }
    return TRUE;
}


/* 4 bytes IP address */
typedef struct ip_address {
    u_char byte1;
    u_char byte2;
    u_char byte3;
    u_char byte4;
}ip_address;

/* IPv4 header */
typedef struct ip_header {
    u_char  ver_ihl; // Version (4 bits) + IP header length (4 bits)
    u_char  tos;     // Type of service 
    u_short tlen;    // Total length 
    u_short identification; // Identification
    u_short flags_fo; // Flags (3 bits) + Fragment offset (13 bits)
    u_char  ttl;      // Time to live
    u_char  proto;    // Protocol
    u_short crc;      // Header checksum
    ip_address  saddr; // Source address
    ip_address  daddr; // Destination address
    u_int  op_pad;     // Option + Padding
}ip_header;

/* UDP header*/
typedef struct udp_header {
    u_short sport; // Source port
    u_short dport; // Destination port
    u_short len;   // Datagram length
    u_short crc;   // Checksum
}udp_header;

/* prototype of the packet handler */
void packet_handler(u_char* param,
    const struct pcap_pkthdr* header,
    const u_char* pkt_data);


int main()
{
    pcap_if_t* alldevs;
    pcap_if_t* d;
    int inum;
    int i = 0;
    pcap_t* adhandle;
    char errbuf[PCAP_ERRBUF_SIZE];
    u_int netmask;
    char packet_filter[] = "ip and udp";
    struct bpf_program fcode;

    /* Load Npcap and its functions. */
    if (!LoadNpcapDlls())
    {
        fprintf(stderr, "Couldn't load Npcap\n");
        exit(1);
    }

    /* Retrieve the device list */
    if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING,
        NULL, &alldevs, errbuf) == -1)
    {
        fprintf(stderr, "Error in pcap_findalldevs: %s\n", errbuf);
        exit(1);
    }

    /* Print the list */
    for (d = alldevs; d; d = d->next)
    {
        printf("%d. %s", ++i, d->name);
        if (d->description)
            printf(" (%s)\n", d->description);
        else
            printf(" (No description available)\n");
    }

    if (i == 0)
    {
        printf("\nNo interfaces found! Make sure Npcap is installed.\n");
        return -1;
    }

    printf("Enter the interface number (1-%d):", i);
    scanf_s("%d", &inum);

    if (inum < 1 || inum > i)
    {
        printf("\nInterface number out of range.\n");
        /* Free the device list */
        pcap_freealldevs(alldevs);
        return -1;
    }

    /* Jump to the selected adapter */
    for (d = alldevs, i = 0; i < inum - 1;d = d->next, i++);

    /* Open the adapter */
    if ((adhandle = pcap_open(d->name, // name of the device
        65536, // portion of the packet to capture. 
               // 65536 grants that the whole packet
               // will be captured on all the MACs.
        PCAP_OPENFLAG_PROMISCUOUS, // promiscuous mode
        1000, // read timeout
        NULL, // remote authentication
        errbuf // error buffer
    )) == NULL)
    {
        fprintf(stderr,
            "\nUnable to open the adapter. %s is not supported by Npcap\n",
            d->name);
        /* Free the device list */
        pcap_freealldevs(alldevs);
        return -1;
    }

    /* Check the link layer. We support only Ethernet for simplicity. */
    if (pcap_datalink(adhandle) != DLT_EN10MB)
    {
        fprintf(stderr, "\nThis program works only on Ethernet networks.\n");
        /* Free the device list */
        pcap_freealldevs(alldevs);
        return -1;
    }

    if (d->addresses != NULL)
        /* Retrieve the mask of the first address of the interface */
        netmask = ((struct sockaddr_in*)(d->addresses->netmask))->sin_addr.S_un.S_addr;
    else
        /* If the interface is without addresses
         * we suppose to be in a C class network */
        netmask = 0xffffff;


    //compile the filter
    if (pcap_compile(adhandle, &fcode, packet_filter, 1, netmask) < 0)
    {
        fprintf(stderr, "\nUnable to compile the packet filter. Check the syntax.\n");
        /* Free the device list */
        pcap_freealldevs(alldevs);
        return -1;
    }

    //set the filter
    if (pcap_setfilter(adhandle, &fcode) < 0)
    {
        fprintf(stderr, "\nError setting the filter.\n");
        /* Free the device list */
        pcap_freealldevs(alldevs);
        return -1;
    }

    printf("\nlistening on %s...\n", d->description);

    /* At this point, we don't need any more the device list. Free it */
    pcap_freealldevs(alldevs);

    /* start the capture */
    pcap_loop(adhandle, 0, packet_handler, NULL);

    return 0;
}

/* Callback function invoked by libpcap for every incoming packet */
void packet_handler(u_char* param,
    const struct pcap_pkthdr* header,
    const u_char* pkt_data)
{
    struct tm ltime;
    char timestr[16];
    ip_header* ih;
    udp_header* uh;
    u_int ip_len;
    u_short sport, dport;
    time_t local_tv_sec;

    /*
     * Unused variable
     */
    (VOID)(param);

    /* convert the timestamp to readable format */
    local_tv_sec = header->ts.tv_sec;
    localtime_s(&ltime, &local_tv_sec);
    strftime(timestr, sizeof timestr, "%H:%M:%S", &ltime);

    /* print timestamp and length of the packet */
    printf("%s.%.6d len:%d ", timestr, header->ts.tv_usec, header->len);

    /* retireve the position of the ip header */
    ih = (ip_header*)(pkt_data +
        14); //length of ethernet header

      /* retireve the position of the udp header */
    ip_len = (ih->ver_ihl & 0xf) * 4;
    uh = (udp_header*)((u_char*)ih + ip_len);

    /* convert from network byte order to host byte order */
    sport = ntohs(uh->sport);
    dport = ntohs(uh->dport);

    /* print ip addresses and udp ports */
    printf("%d.%d.%d.%d.%d -> %d.%d.%d.%d.%d\n",
        ih->saddr.byte1,
        ih->saddr.byte2,
        ih->saddr.byte3,
        ih->saddr.byte4,
        sport,
        ih->daddr.byte1,
        ih->daddr.byte2,
        ih->daddr.byte3,
        ih->daddr.byte4,
        dport);
}

运行如下:

首先,我们将过滤器设置为“ip and udp”。通过这种方式,我们可以确定 packet_handler() 将只接收 IPv4 上的 UDP 数据包:这简化了解析并提高了程序的效率。

我们还创建了几个描述 IP 和 UDP 标头的结构。packet_handler() 使用这些结构来正确定位各种标头字段。

packet_handler() 虽然仅限于单个协议解析器(UDP over IPv4),但它显示了 tcpdump/WinDump 等复杂的“嗅探器”如何解码网络流量。由于我们对 MAC 标头不感兴趣,因此我们跳过它。为简单起见,在开始捕获之前,我们使用 pcap_datalink() 检查 MAC 层,以确保我们正在处理以太网网络。这样我们就可以确定 MAC 报头正好是 14 个字节。

IP 标头位于 MAC 标头之后。我们将从 IP 标头中提取 IP 源地址和目标地址。

到达 UDP 报头有点复杂,因为 IP 报头没有固定的长度。因此,我们使用 IP 头的长度字段来知道它的大小。一旦我们知道 UDP 标头的位置,我们就提取源端口和目标端口。

习题与思考题

1、WINPCAP(Npcap)是否能实现服务质量的控制?

答:不能。WinPcap可以独立地通过主机协议发送和接受数据,如同TCP-IP。这就意味着WinPcap不能阻止、过滤或操纵同一机器上的其他应用程序的通讯:它仅仅能简单地"监视"在网络上传输的数据包。所以,它不能提供类似网络流量控制、服务质量调度和个人防火墙之类的支持,因而不能实现服务质量的控制。


参考:
1、https://npcap.com/guide/npcap-tutorial.html

实验三 协议分析程序的编写

实验目的

  1. 目的:分析IP协议,统计流量。大家可以把流量计做成流量地图,显示不同地点间数据实时发送的量,比如武汉到北京的数据包发送的数据量。 可以用echart画地图,用whois获取IP地址的所属地。

  2. 任务:根据IP协议,解析每个数据包的PCI,并对不同IP进行流量统计。

  3. 任务的说明:

    • 实现的软件能显示每个包的PCI。
    • 能显示每个IP地址的流量,即显示接收包的数量。

实验原理

首先解释一下 PCI 的具体含义:

使用分层网络方法,每一层都会“封装”从其上一层向下传递的数据,同时添加自己的信息。然后,该层会将新的 PDU 向下传递到下一层(或者一旦您到达第 1 层,就将其通过物理介质发送)。一旦到达另一端,则采取相反的路径;每一层查看与其相关的标头/尾标,处理它们,并将有效负载向上传递到下一层。 每个封装级别的具体信息取决于协议。例如,TCP 将添加源端口和目标端口、校验和、序列/确认号以及其他一些东西。 IP 添加源/目标地址、数据包长度和其他一些内容。以太网将添加源/目标 MAC、EtherType、FCS 等。

然后就是利用实验二中使用的 npcap 进行抓包。这里主要是抓取 TCP 的数据包,然后将抓取到的每一个数据包解析之后,将它们的源 ip 和目标 ip 都存放到 MySQL 数据库中。然后使用 Python 调用解析 ip 归属地的接口,将每一个 ip 的归属地解析好,然后更新数据库中的相关字段。

之后使用 Spring Boot 搭建一个简单的后台接口,主要是给前端网页提供 json 数据。

前端主要使用 echarts 来绘图,绘图所需要的数据就来自上面 Spring Boot 提供的 json 数据接口。这样就可以实时地绘制我们所需的流量地图了。

实验环境

系统:Windows 10 家庭中文版 21H2

Visual Studio 版本:Visual Studio 2022

Npcap 版本:1.60

Npcap SDK 版本:1.12

MySQL 版本:8.0.28

jdk 版本:openjdk version "17.0.2"

Python 版本:3.10.4

echart 版本:

实验内容

分析IP协议,统计流量。把流量计做成流量地图,显示不同地点间数据实时发送的量,比如武汉到北京的数据包发送的数据量。用echart画地图,用whois获取IP地址的所属地。

实验过程

首先,编写 C++ 程序进行抓包,这个在实验二中已经实现,这里就不再赘述。

这里主要介绍一下将捕获到的源 ip 地址和目标 ip 地址存放到数据库中的过程。

首先,配置环境,步骤如下:

之后,还要将 libmysql.lib 文件添加到项目的根目录下:

然后我们写一个 C++ 的类,从而实现对数据库中数据进行增删改查:

//#include "stdafx.h"
#include "DBMySQL.h"

VspdCToMySQL::VspdCToMySQL()
{
}

VspdCToMySQL::~VspdCToMySQL()
{
}

//初始化数据
int VspdCToMySQL::ConnMySQL(const char* host, const char* port, const char* Db, const char* user, const char* passwd, const char* const charset, const char* Msg)
{
	if (mysql_init(&mysql) == NULL)
	{
		Msg = "inital mysql handle error";
		return 1;
	}

	if (mysql_real_connect(&mysql, host, user, passwd, Db, 0, NULL, 0) == NULL)
	{
		Msg = "Failed to connect to database: Error";
		return 1;
	}

	if (mysql_set_character_set(&mysql, charset) != 0)
	{
		Msg = "mysql_set_character_set Error";
		return 1;
	}
	return 0;
}

//查询数据
string VspdCToMySQL::SelectData(const char* SQL, int Cnum, const char* Msg)
{
	MYSQL_ROW m_row;
	MYSQL_RES* m_res;
	char sql[2048];
	sprintf(sql, SQL);
	int rnum = 0;
	char rg = '\n';//行隔开
	// char rg='\r';
	char cg = '\t';//字段隔开
	if (mysql_query(&mysql, sql) != 0)
	{
		Msg = "select ps_info Error";
		return "";
	}
	m_res = mysql_store_result(&mysql);

	if (m_res == NULL)
	{
		Msg = "select username Error";
		return "";
	}
	string str("");
	while (m_row = mysql_fetch_row(m_res))
	{
		for (int i = 0; i < Cnum; i++)
		{
			str += m_row[i];
			str += cg;
		}
		str += rg;
		rnum++;
	}

	mysql_free_result(m_res);

	return str;
}

//插入数据
int VspdCToMySQL::InsertData(const char* SQL, const char* Msg)
{
	char sql[2048];
	sprintf(sql, SQL);
	if (mysql_query(&mysql, sql) != 0)
	{
		Msg = "Insert Data Error";
		return 1;
	}
	return 0;
}

//更换数据
int VspdCToMySQL::ReplaceData(const char* SQL, const char* Msg)
{
	char sql[2048];
	sprintf(sql, SQL);
	if (mysql_query(&mysql, sql) != 0)
	{
		Msg = "Replace Data Error";
		return 1;
	}
	return 0;
}

//更新数据
int VspdCToMySQL::UpdateData(const char* SQL, const char* Msg)
{
	char sql[2048];
	sprintf(sql, SQL);
	if (mysql_query(&mysql, sql) != 0)
	{
		Msg = "Update Data Error";
		return 1;
	}
	return 0;
}

//删除数据
int VspdCToMySQL::DeleteData(const char* SQL, const char* Msg)
{
	char sql[2048];
	sprintf(sql, SQL);
	if (mysql_query(&mysql, sql) != 0)
	{
		Msg = "Delete Data error";
		return 1;
	}
	return 0;
}

//调用数据库存储过程
int VspdCToMySQL::CallProcedure(const char* SQL, const char* Msg)
{
	char sql[2048];
	sprintf(sql, SQL);
	if (mysql_query(&mysql, sql) != 0)
	{
		Msg = "Call Procedure error";
		return 1;
	}
	return 0;
}
//关闭数据库连接
void VspdCToMySQL::CloseMySQLConn()
{
	mysql_close(&mysql);
	printf("disconnect database\n");
}

然后在抓包程序的回调函数中增加对数据库的操作即可:

/* Callback function invoked by libpcap for every incoming packet */
void packet_handler(u_char* param,
    const struct pcap_pkthdr* header,
    const u_char* pkt_data)
{
    struct tm ltime;
    char timestr[16];
    ip_header* ih;
    udp_header* uh;
    u_int ip_len;
    u_short sport, dport;
    time_t local_tv_sec;

    /*
     * Unused variable
     */
    (VOID)(param);

    /* convert the timestamp to readable format */
    local_tv_sec = header->ts.tv_sec;
    localtime_s(&ltime, &local_tv_sec);
    strftime(timestr, sizeof timestr, "%H:%M:%S", &ltime);

    /* print timestamp and length of the packet */
    printf("%s.%.6d len:%d ", timestr, header->ts.tv_usec, header->len);

    /* retireve the position of the ip header */
    ih = (ip_header*)(pkt_data +
        14); //length of ethernet header

      /* retireve the position of the udp header */
    ip_len = (ih->ver_ihl & 0xf) * 4;
    uh = (udp_header*)((u_char*)ih + ip_len);

    /* convert from network byte order to host byte order */
    sport = ntohs(uh->sport);
    dport = ntohs(uh->dport);

    /* print ip addresses and udp ports */
    printf("%d.%d.%d.%d.%d -> %d.%d.%d.%d.%d\n",
        ih->saddr.byte1,
        ih->saddr.byte2,
        ih->saddr.byte3,
        ih->saddr.byte4,
        sport,
        ih->daddr.byte1,
        ih->daddr.byte2,
        ih->daddr.byte3,
        ih->daddr.byte4,
        dport);

    // 将源 ip 地址和目的 ip 地址转换成字符串
    string srcIp = to_string(ih->saddr.byte1) + "." + to_string(ih->saddr.byte2) + "." + to_string(ih->saddr.byte3) + "." + to_string(ih->saddr.byte4);
    string dstIp = to_string(ih->daddr.byte1) + "." + to_string(ih->daddr.byte2) + "." + to_string(ih->daddr.byte3) + "." + to_string(ih->daddr.byte4);
    cout << "src ip  " << srcIp << "\t" << endl;
    cout << "dst ip  " << dstIp << "\t" << endl;
    cout << "=========" << endl;
    string SQLString = "insert into test_tb(id, src_ip, dst_ip) values(" + to_string(++id) + ", '" + srcIp + "', "
        + "'" + dstIp + "')";
    cout << SQLString << endl;
    // 插入数据
    /*const char* SQL = "insert into test_tb(id, src_ip, dst_ip) values(4, 'qingxiaofeng', 'fsdajfjsad')";
    */
    const char* SQL = SQLString.c_str();
    const char* Msg = "";
    if (vspdctomysql->InsertData(SQL, Msg) == 0)
    {
        printf("insert success\n");
    }
}

这里的逻辑是每一次启动这个抓包程序时,会自动清空上一次在数据库中存储的数据,然后再将本次抓取到的数据存放到数据库中。

之后就是 Pyhton 实现对 ip 地址的解析,并将解析好的结果更新到数据库中:

import time
from mysql.connector import MySQLConnection, Error
from python_mysql_dbconfig import read_db_config
import requests


def getJson(ip):
    myUrl = 'https://ip.useragentinfo.com/json?ip=' + ip
    # myUrl = 'http://whois.pconline.com.cn/ipJson.jsp?ip=' + ip + '&json=true'
    # ip = '40.79.150.121'
    r = requests.get(myUrl)
    return r.json()


def query_with_fetchall():
    try:
        dbconfig = read_db_config()
        conn = MySQLConnection(**dbconfig)
        cursor = conn.cursor()
        cursor.execute("SELECT * FROM test_tb")
        rows = cursor.fetchall()

        print('Total Row(s):', cursor.rowcount)
        res = []
        for row in rows:
            if row[3] == None and row[4] == None:
                res.append(row)
        # print(res)
        return res
    except Error as e:
        print(e)

    finally:
        cursor.close()
        conn.close()


def update_testTb():
    cityData = ["北京", "天津", "上海", "重庆", "石家庄", "唐山", "秦皇岛", "邯 郸",
                "邢台", "保定", "张家口", "承德", "沧州", "廊坊", "衡水", "太原", "大同",
                "阳泉", "长治", "晋城", "朔州", "晋中", "运城", "忻州", "临汾", "吕梁",
                "呼和浩特", "包头", "乌海", "赤峰", "通辽", "鄂尔多斯", "呼伦贝尔", "巴彦淖尔",
                "乌兰察布", "兴安", "锡林郭勒", "阿拉善", "沈阳", "大连", "鞍山", "抚顺",
                "本溪", "丹东", "锦州", "营口", "阜新", "辽阳", "盘锦", "铁岭", "朝阳",
                "葫芦岛", "长春", "吉林", "四平", "辽源", "通化", "白山", "松原", "白城",
                "延边朝鲜族自治", "哈尔滨", "齐齐哈尔", "鸡西", "鹤岗", "双鸭山", "大庆",
                "伊春", "佳木斯", "七台河", "牡丹江", "黑河", "绥化", "大兴安岭地", "南京",
                "无锡", "徐州", "常州", "苏州", "南通", "连云港", "淮安", "盐城", "扬州",
                "镇江", "泰州", "宿迁", "杭州", "宁波", "温州", "嘉兴", "湖州", "绍兴",
                "金华", "衢州", "舟山", "台州", "丽水", "合肥", "芜湖", "蚌埠", "淮南",
                "马鞍山", "淮北", "铜陵", "安庆", "黄山", "滁州", "阜阳", "宿州", "巢湖",
                "六安", "亳州", "池州", "宣城", "福州", "厦门", "莆田", "三明", "泉州",
                "漳州", "南平", "龙岩", "宁德", "南昌", "景德镇", "萍乡", "九江", "新余",
                "鹰潭", "赣州", "吉安", "宜春", "抚州", "上饶", "济南", "青岛", "淄博",
                "枣庄", "东营", "烟台", "潍坊", "济宁", "泰安", "威海", "日照", "莱芜",
                "临沂", "德州", "聊城", "滨州", "郑州", "开封", "洛阳", "平顶山", "安阳",
                "鹤壁", "新乡", "焦作", "濮阳", "许昌", "漯河", "三门峡", "南阳", "商丘",
                "信阳", "周口", "驻马店", "武汉", "黄石", "十堰", "宜昌", "襄樊", "鄂州",
                "荆门", "孝感", "荆州", "黄冈", "咸宁", "随州", "恩施土家族苗族自治", "长沙",
                "株洲", "湘潭", "衡阳", "邵阳", "岳阳", "常德", "张家界", "益阳", "郴州",
                "永州", "怀化", "娄底", "湘西土家族苗族自治", "广州", "韶关", "深圳", "珠海",
                "汕头", "佛山", "江门", "湛江", "茂名", "肇庆", "惠州", "梅州", "汕尾", "河源",
                "阳江", "清远", "东莞", "中山", "潮州", "揭阳", "云浮", "南宁", "柳州",
                "桂林", "梧州", "北海", "防城港", "钦州", "贵港", "玉林", "百色", "贺州",
                "河池", "来宾", "崇左", "海口", "三亚", "成都", "自贡", "攀枝花", "泸州",
                "德阳", "绵阳", "广元", "遂宁", "内江", "乐山", "南充", "眉山", "宜宾", "广安",
                "达州", "雅安", "巴中", "资阳", "阿坝藏族羌族自治", "甘孜藏族自治", "凉山彝族自治",
                "贵阳", "六盘水", "遵义", "安顺", "铜仁地", "黔西南布依族苗族自治", "毕节地",
                "黔东南苗族侗族自治", "黔南布依族苗族自治", "昆明", "曲靖", "玉溪", "保山",
                "昭通", "丽江", "临沧", "楚雄彝族自治", "红河哈尼族彝族自治", "文山壮族苗族自治",
                "西双版纳傣族自治", "大理白族自治", "德宏傣族景颇族自治", "怒江傈僳族自治",
                "迪庆藏族自治", "拉萨", "昌都地", "山南地", "日喀则地", "那曲地", "阿里地",
                "林芝地", "西安", "铜川", "宝鸡", "咸阳", "渭南", "延安", "汉中", "榆林",
                "安康", "商洛", "兰州", "嘉峪关", "金昌", "白银", "天水", "武威", "张掖",
                "平凉", "酒泉", "庆阳", "定西", "陇南", "临夏回族自治", "甘南藏族自治", "西宁",
                "海东地", "海北藏族自治", "黄南藏族自治", "海南藏族自治", "果洛藏族自治",
                "玉树藏族自治", "海西蒙古族藏族自治", "银川", "石嘴山", "吴忠", "固原", "中卫",
                "乌鲁木齐", "克拉玛依", "吐鲁番地", "哈密地", "昌吉回族自治", "博尔塔拉蒙古自治",
                "巴音郭楞蒙古自治", "阿克苏地", "克孜勒苏柯尔克孜自治", "喀什地", "和田地",
                "伊犁哈萨克自治", "塔城地", "阿勒泰地", "仙桃", "潜江", "天门", "神农架林",
                "五指山", "琼海", "儋州", "文昌", "万宁", "东方", "定安县", "屯昌县", "澄迈县",
                "临高县", "白沙黎族自治县", "昌江黎族自治县", "乐东黎族自治县", "陵水黎族自治县",
                "保亭黎族苗族自治县", "琼中黎族苗族自治县", "石河子"]
    # read database configuration
    db_config = read_db_config()

    toBeProcessedData = query_with_fetchall()

    # prepare query and data
    query = """ UPDATE test_tb
                SET src_loc = %s, dst_loc = %s
                WHERE id = %s """

    try:
        conn = MySQLConnection(**db_config)

        # update book title
        cursor = conn.cursor()
        # cursor.execute(query, data)

        for row in toBeProcessedData:
            rowId = row[0]
            srcIp = row[1]
            dstIp = row[2]

            if srcIp == '10.16.80.216': # 这里要替换成本机的 ip
                srcLoc = '武汉'
            else:
                srcJsonData = getJson(srcIp)
                print(srcJsonData)
                srcLoc = srcJsonData['city'][0:-1]
            time.sleep(1)
            if dstIp == '10.16.80.216': # 这里要替换成本机的 ip
                dstLoc = '武汉'
            else:
                dstJsonData = getJson(dstIp)
                dstLoc = dstJsonData['city'][0:-1]
            time.sleep(1)

            if srcLoc != '' and srcLoc not in cityData:
                srcLoc = '海外'
            if dstLoc != '' and dstLoc not in cityData:
                dstLoc = '海外'

            print(srcLoc + dstLoc)
            print(row)
            print('==========分割线=========')
            data = (srcLoc, dstLoc, rowId)
            cursor.execute(query, data)
            conn.commit()

        # accept the changes
        conn.commit()

    except Error as error:
        print(error)

    finally:
        cursor.close()
        conn.close()


if __name__ == '__main__':
    while True:
        update_testTb()

再之后,使用 Spring Boot 创建一个 json 接口,主要的逻辑如下:

/**
* 过滤出同时具有 srcLoc 和 dstLoc 并且都是中国城市的数据
* 
* @return
* @throws IOException
* @throws InterruptedException
*/
@GetMapping("/getJson")
public List<TestTb> getJsonData() {
    // 先从数据库中取出数据
    List<TestTb> testTbList = testTbService.list();
    List<TestTb> filtedList = new ArrayList<>();

    // 过滤
    for (TestTb testTb : testTbList) {
        if (testTb.getSrcLoc() != null
                && testTb.getSrcLoc().length() != 0
                && !testTb.getSrcLoc().equals("海外")
                && testTb.getDstLoc() != null
                && testTb.getDstLoc().length() != 0
                && !testTb.getDstLoc().equals("海外")) {
            filtedList.add(testTb);
            System.out.println(testTb);
        }
    }
    // System.out.println(filtedList);

    return filtedList;
}

最后,是前端的逻辑:

async function getJsonData(url) {
    let obj = await (await fetch(url)).json();
    return obj;
}

console.log('test');

url = "http://localhost:8080/testTb/getJson"
getJsonData(url).then((jsonData) => {
    // console.log(jsonData);

    var geoCoordMap = {
        '北京': [116.398, 39.9082],
        '天津': [117.252, 39.1039],
        '上海': [121.476, 31.2244],
        '重庆': [106.548, 29.5549],
        '石家庄': [114.476, 38.0483],
        '唐山': [118.167, 39.6353],
        '秦皇岛': [119.59, 39.9479],
        '邯郸': [114.489, 36.5994],
        '邢台': [114.504, 37.0717],
        '保定': [115.466, 38.8799],
        '张家口': [114.89, 40.8262],
        '承德': [117.935, 40.9955],
        '沧州': [116.866, 38.3124],
        '廊坊': [116.706, 39.5193],
        '衡水': [115.698, 37.7354],
        '太原': [112.531, 37.8551],
        '大同': [113.3, 40.0784],
        '阳泉': [113.58, 37.8567],
        '长治': [113.117, 36.1922],
        '晋城': [112.853, 35.4906],
        '朔州': [112.429, 39.3186],
        '晋中': [112.752, 37.6883],
        '运城': [110.998, 35.0315],
        '忻州': [112.735, 38.4158],
        '临汾': [111.527, 36.1021],
        '吕梁': [111.13, 37.5203],
        '呼和浩特': [111.667, 40.8083],
        '包头': [109.871, 40.6617],
        '乌海': [106.814, 39.6692],
        '赤峰': [118.957, 42.2673],
        '通辽': [122.261, 43.6059],
        '鄂尔多斯': [110.003, 39.8223],
        '呼伦贝尔': [119.756, 49.2458],
        '巴彦淖尔': [107.376, 40.7801],
        '乌兰察布': [113.121, 41.0317],
        '兴安': [122.135, 46.0872],
        '锡林郭勒': [116.099, 43.9455],
        '阿拉善': [105.686, 38.8464],
        '沈阳': [123.434, 41.8057],
        '大连': [121.617, 38.9143],
        '鞍山': [123.002, 41.1158],
        '抚顺': [123.924, 41.8678],
        '本溪': [123.786, 41.2984],
        '丹东': [124.398, 40.1325],
        '锦州': [121.148, 41.1261],
        '营口': [122.25, 40.6716],
        '阜新': [121.654, 42.0143],
        '辽阳': [123.179, 41.2778],
        '盘锦': [122.071, 41.1188],
        '铁岭': [123.843, 42.2867],
        '朝阳': [120.45, 41.5735],
        '葫芦岛': [120.856, 40.7481],
        '长春': [125.325, 43.8869],
        '吉林': [126.571, 43.8458],
        '四平': [124.398, 43.1725],
        '辽源': [125.145, 42.9058],
        '通化': [125.94, 41.7292],
        '白山': [126.424, 41.9401],
        '松原': [124.824, 45.1409],
        '白城': [122.838, 45.6204],
        '延边朝鲜族自治': [129.498, 42.8942],
        '哈尔滨': [126.652, 45.7613],
        '齐齐哈尔': [123.937, 47.3416],
        '鸡西': [130.97, 45.2952],
        '鹤岗': [130.279, 47.3311],
        '双鸭山': [131.16, 46.6469],
        '大庆': [125.094, 46.5883],
        '伊春': [128.901, 47.7247],
        '佳木斯': [130.37, 46.8075],
        '七台河': [131.008, 45.7719],
        '牡丹江': [129.623, 44.5848],
        '黑河': [127.501, 50.2479],
        '绥化': [126.99, 46.637],
        '大兴安岭地': [124.397, 51.6737],
        '南京': [118.792, 32.0562],
        '无锡': [120.359, 31.5611],
        '徐州': [117.174, 34.2586],
        '常州': [119.975, 31.7698],
        '苏州': [120.639, 31.3205],
        '南通': [120.871, 32.0093],
        '连云港': [119.173, 34.5959],
        '淮安': [119.015, 33.612],
        '盐城': [120.139, 33.3794],
        '扬州': [119.413, 32.4195],
        '镇江': [119.442, 32.211],
        '泰州': [119.921, 32.4617],
        '宿迁': [118.297, 33.9539],
        '杭州': [120.173, 30.274],
        '宁波': [121.544, 29.8683],
        '温州': [120.638, 28.027],
        '嘉兴': [120.759, 30.7539],
        '湖州': [120.097, 30.8706],
        '绍兴': [120.586, 29.9958],
        '金华': [119.652, 29.0896],
        '衢州': [118.874, 28.9359],
        '舟山': [122.106, 30.0132],
        '台州': [121.456, 28.6708],
        '丽水': [119.917, 28.4522],
        '合肥': [117.296, 31.8693],
        '芜湖': [118.376, 31.3273],
        '蚌埠': [117.402, 32.9302],
        '淮南': [117.0, 32.6383],
        '马鞍山': [118.502, 31.693],
        '淮北': [116.793, 33.969],
        '铜陵': [117.811, 30.9446],
        '安庆': [117.051, 30.527],
        '黄山': [118.317, 29.7099],
        '滁州': [118.318, 32.3011],
        '阜阳': [115.82, 32.8971],
        '宿州': [116.984, 33.6381],
        '巢湖': [117.87, 31.6009],
        '六安': [116.491, 31.7426],
        '亳州': [115.783, 33.8696],
        '池州': [117.489, 30.6557],
        '宣城': [118.755, 30.9469],
        '福州': [119.306, 26.0761],
        '厦门': [118.117, 24.4838],
        '莆田': [119.005, 25.434],
        '三明': [117.64, 26.2681],
        '泉州': [118.585, 24.9295],
        '漳州': [117.666, 24.5124],
        '南平': [118.179, 26.6399],
        '龙岩': [117.033, 25.0946],
        '宁德': [119.537, 26.6528],
        '南昌': [115.897, 28.6788],
        '景德镇': [117.193, 29.298],
        '萍乡': [113.852, 27.6227],
        '九江': [115.99, 29.7156],
        '新余': [114.918, 27.8163],
        '鹰潭': [117.034, 28.2394],
        '赣州': [114.942, 25.8553],
        '吉安': [114.987, 27.1136],
        '宜春': [114.399, 27.8068],
        '抚州': [116.363, 27.9793],
        '上饶': [117.969, 28.4456],
        '济南': [117.021, 36.6594],
        '青岛': [120.357, 36.084],
        '淄博': [118.05, 36.8063],
        '枣庄': [117.309, 34.8127],
        '东营': [118.675, 37.4321],
        '烟台': [121.392, 37.5379],
        '潍坊': [119.122, 36.7159],
        '济宁': [116.588, 35.4133],
        '泰安': [117.121, 36.1813],
        '威海': [122.122, 37.5043],
        '日照': [119.526, 35.4188],
        '莱芜': [117.679, 36.2136],
        '临沂': [118.346, 35.0538],
        '德州': [116.302, 37.4545],
        '聊城': [115.986, 36.4561],
        '滨州': [118.016, 37.3832],
        '郑州': [113.663, 34.7619],
        '开封': [114.34, 34.7893],
        '洛阳': [112.401, 34.6567],
        '平顶山': [113.304, 33.7274],
        '安阳': [114.346, 36.1112],
        '鹤壁': [114.297, 35.7481],
        '新乡': [113.89, 35.3015],
        '焦作': [113.228, 35.2338],
        '濮阳': [115.029, 35.7632],
        '许昌': [113.832, 34.0222],
        '漯河': [114.036, 33.5673],
        '三门峡': [111.192, 34.7758],
        '南阳': [112.534, 32.9993],
        '商丘': [115.651, 34.4389],
        '信阳': [114.075, 32.124],
        '周口': [114.653, 33.6166],
        '驻马店': [114.028, 32.9832],
        '武汉': [114.281, 30.5781],
        '黄石': [115.06, 30.2063],
        '十堰': [110.782, 32.6553],
        '宜昌': [111.295, 30.7012],
        '襄樊': [112.135, 32.062],
        '鄂州': [114.904, 30.3898],
        '荆门': [112.196, 31.0277],
        '孝感': [113.919, 30.9246],
        '荆州': [112.245, 30.3252],
        '黄冈': [114.875, 30.4465],
        '咸宁': [114.336, 29.8337],
        '随州': [113.375, 31.7167],
        '恩施土家族苗族自治': [109.482, 30.2899],
        '长沙': [113.009, 28.2085],
        '株洲': [113.149, 27.8406],
        '湘潭': [112.907, 27.8612],
        '衡阳': [112.612, 26.8962],
        '邵阳': [111.467, 27.2371],
        '岳阳': [113.108, 29.3663],
        '常德': [111.703, 29.0366],
        '张家界': [110.477, 29.1197],
        '益阳': [112.356, 28.58],
        '郴州': [113.032, 25.7968],
        '永州': [111.608, 26.438],
        '怀化': [109.977, 27.5484],
        '娄底': [111.998, 27.7259],
        '湘西土家族苗族自治': [109.601, 27.9472],
        '广州': [113.322, 23.1341],
        '韶关': [113.598, 24.8024],
        '深圳': [114.056, 22.5421],
        '珠海': [113.547, 22.2532],
        '汕头': [116.724, 23.3601],
        '佛山': [113.121, 23.0305],
        '江门': [113.085, 22.583],
        '湛江': [110.421, 21.1989],
        '茂名': [110.927, 21.6616],
        '肇庆': [112.476, 23.0734],
        '惠州': [114.417, 23.0896],
        '梅州': [116.118, 24.2988],
        '汕尾': [115.368, 22.7782],
        '河源': [114.697, 23.7503],
        '阳江': [111.984, 21.872],
        '清远': [113.045, 23.6882],
        '东莞': [113.759, 23.0384],
        '中山': [113.372, 22.525],
        '潮州': [116.642, 23.6713],
        '揭阳': [116.37, 23.5276],
        '云浮': [112.044, 22.9294],
        '南宁': [108.311, 22.8178],
        '柳州': [109.413, 24.3068],
        '桂林': [110.285, 25.2827],
        '梧州': [111.304, 23.5036],
        '北海': [109.123, 21.4788],
        '防城港': [108.346, 21.6146],
        '钦州': [108.609, 21.9509],
        '贵港': [109.606, 23.0939],
        '玉林': [110.151, 22.6255],
        '百色': [106.621, 23.9018],
        '贺州': [111.563, 24.4279],
        '河池': [108.062, 24.6972],
        '来宾': [109.222, 23.7351],
        '崇左': [107.339, 22.4248],
        '海口': [110.32, 20.0323],
        '三亚': [109.514, 18.2355],
        '成都': [104.066, 30.6581],
        '自贡': [104.77, 29.3534],
        '攀枝花': [101.699, 26.5733],
        '泸州': [105.444, 28.8889],
        '德阳': [104.388, 31.1368],
        '绵阳': [104.726, 31.467],
        '广元': [105.834, 32.4384],
        '遂宁': [105.583, 30.5137],
        '内江': [105.063, 29.5825],
        '乐山': [103.746, 29.5866],
        '南充': [106.095, 30.7844],
        '眉山': [103.826, 30.0389],
        '宜宾': [104.621, 28.7705],
        '广安': [106.642, 30.4706],
        '达州': [107.488, 31.2126],
        '雅安': [103.015, 29.9937],
        '巴中': [106.755, 31.8574],
        '资阳': [104.651, 30.1286],
        '阿坝藏族羌族自治': [102.222, 31.8992],
        '甘孜藏族自治': [101.866, 30.1414],
        '凉山彝族自治': [102.242, 27.9095],
        '贵阳': [106.713, 26.5768],
        '六盘水': [104.873, 26.5821],
        '遵义': [106.931, 27.6926],
        '安顺': [105.927, 26.2573],
        '铜仁地': [109.188, 27.7184],
        '黔西南布依族苗族自治': [104.895, 25.0935],
        '毕节地': [105.288, 27.3032],
        '黔东南苗族侗族自治': [107.981, 26.5831],
        '黔南布依族苗族自治': [107.511, 26.283],
        '昆明': [102.703, 25.0499],
        '曲靖': [103.784, 25.4913],
        '玉溪': [102.536, 24.3472],
        '保山': [99.1647, 25.1249],
        '昭通': [103.704, 27.3656],
        '丽江': [100.277, 26.9229],
        '临沧': [100.085, 23.8971],
        '楚雄彝族自治': [101.545, 25.0549],
        '红河哈尼族彝族自治': [103.18, 23.4301],
        '文山壮族苗族自治': [104.255, 23.3803],
        '西双版纳傣族自治': [100.799, 22.0109],
        '大理白族自治': [100.222, 25.5842],
        '德宏傣族景颇族自治': [98.5669, 24.4484],
        '怒江傈僳族自治': [98.903, 25.8583],
        '迪庆藏族自治': [99.709, 27.859],
        '拉萨': [91.1189, 29.6553],
        '昌都地': [97.1503, 31.1816],
        '山南地': [91.805, 29.2407],
        '日喀则地': [88.8884, 29.2717],
        '那曲地': [92.0593, 31.4524],
        '阿里地': [80.1027, 32.5517],
        '林芝地': [94.4083, 29.6352],
        '西安': [108.954, 34.2649],
        '铜川': [108.948, 34.9005],
        '宝鸡': [107.139, 34.3698],
        '咸阳': [108.71, 34.3268],
        '渭南': [109.51, 34.5073],
        '延安': [109.496, 36.5948],
        '汉中': [107.031, 33.0799],
        '榆林': [109.754, 38.2954],
        '安康': [109.031, 32.6917],
        '商洛': [109.943, 33.8825],
        '兰州': [103.835, 36.0599],
        '嘉峪关': [98.2536, 39.8001],
        '金昌': [102.175, 38.4764],
        '白银': [104.151, 36.5506],
        '天水': [105.702, 34.5875],
        '武威': [102.634, 37.9317],
        '张掖': [100.451, 38.931],
        '平凉': [106.725, 35.5097],
        '酒泉': [98.5139, 39.7563],
        '庆阳': [107.862, 35.9344],
        '定西': [104.622, 35.5852],
        '陇南': [104.934, 33.4695],
        '临夏回族自治': [103.201, 35.5914],
        '甘南藏族自治': [102.899, 34.9854],
        '西宁': [101.777, 36.6172],
        '海东地': [102.086, 36.5092],
        '海北藏族自治': [100.973, 36.9433],
        '黄南藏族自治': [102.027, 35.5399],
        '海南藏族自治': [100.638, 36.3244],
        '果洛藏族自治': [100.295, 34.4775],
        '玉树藏族自治': [97.013, 33.0183],
        '海西蒙古族藏族自治': [97.3632, 37.4214],
        '银川': [106.283, 38.4628],
        '石嘴山': [106.386, 39.0146],
        '吴忠': [106.194, 37.9852],
        '固原': [106.281, 36.011],
        '中卫': [105.686, 37.4829],
        '乌鲁木齐': [87.6168, 43.8235],
        '克拉玛依': [84.9312, 45.5964],
        '吐鲁番地': [89.2107, 43.007],
        '哈密地': [93.5502, 42.8991],
        '昌吉回族自治': [87.9882, 44.0044],
        '博尔塔拉蒙古自治': [82.0787, 44.9126],
        '巴音郭楞蒙古自治': [86.1827, 41.8974],
        '阿克苏地': [80.2445, 41.1239],
        '克孜勒苏柯尔克孜自治': [76.67, 39.8878],
        '喀什地': [75.9994, 39.4657],
        '和田地': [79.7641, 37.2296],
        '伊犁哈萨克自治': [81.3383, 43.9233],
        '塔城地': [83.0148, 46.7523],
        '阿勒泰地': [88.069, 47.81],
        '仙桃': [113.453, 30.3646],
        '潜江': [112.894, 30.4198],
        '天门': [113.164, 30.6513],
        '神农架林': [110.676, 31.7448],
        '五指山': [109.517, 18.7773],
        '琼海': [110.46, 19.248],
        '儋州': [109.579, 19.5199],
        '文昌': [110.748, 19.6159],
        '万宁': [110.388, 18.8122],
        '东方': [108.693, 19.0807],
        '定安县': [110.447, 19.4883],
        '屯昌县': [110.097, 19.3686],
        '澄迈县': [110.016, 19.7302],
        '临高县': [109.689, 19.8973],
        '白沙黎族自治县': [109.44, 19.2375],
        '昌江黎族自治县': [109.036, 19.3083],
        '乐东黎族自治县': [109.175, 18.7435],
        '陵水黎族自治县': [110.029, 18.5167],
        '保亭黎族苗族自治县': [109.7, 18.6263],
        '琼中黎族苗族自治县': [109.832, 19.0467],
        '石河子': [86.0526, 44.3051]
    };

    var WHData = []

    for (let i = 0; i < jsonData.length; i++) {
        // console.log(jsonData[i]);
        tmp = [];
        tmp.push({ name: jsonData[i].srcLoc });
        tmp.push({ name: jsonData[i].dstLoc, value: 10 });
        WHData.push(tmp);
    }

    var planePath = 'path://M1705.06,1318.313v-89.254l-319.9-221.799l0.073-208.063c0.521-84.662-26.629-121.796-63.961-121.491c-37.332-0.305-64.482,36.829-63.961,121.491l0.073,208.063l-319.9,221.799v89.254l330.343-157.288l12.238,241.308l-134.449,92.931l0.531,42.034l175.125-42.917l175.125,42.917l0.531-42.034l-134.449-92.931l12.238-241.308L1705.06,1318.313z';
    planePath = "arrow";

    var convertData = function (data) {
        var res = [];
        for (var i = 0; i < data.length; i++) {
            var dataItem = data[i];
            var fromCoord = geoCoordMap[dataItem[0].name];
            var toCoord = geoCoordMap[dataItem[1].name];
            if (fromCoord && toCoord) {
                res.push([{
                    coord: fromCoord
                }, {
                    coord: toCoord
                }]);
            }
        }
        return res;
    };

    var color = ['#a6c84c', '#ffa022', '#46bee9'];
    var series = [];
    [['Beijing', WHData]].forEach(function (item, i) {
        series.push({
            name: item[0],
            type: 'lines',
            zlevel: 1,
            effect: {
                show: true,
                period: 6,
                trailLength: 0.7,
                color: '#fff',
                symbolSize: 3
            },
            lineStyle: {
                normal: {
                    color: color[i],
                    width: 0,
                    curveness: 0.2
                }
            },
            data: convertData(item[1])
        },
            {
                name: item[0],
                type: 'lines',
                zlevel: 2,
                effect: {
                    show: true,
                    period: 6,
                    trailLength: 0,
                    symbol: planePath,
                    symbolSize: 8
                },
                lineStyle: {
                    normal: {
                        color: color[i],
                        width: 1,
                        opacity: 0.4,
                        curveness: 0.2
                    }
                },
                data: convertData(item[1])
            },
            {
                name: item[0],
                type: 'effectScatter',
                coordinateSystem: 'geo',
                zlevel: 2,
                rippleEffect: {
                    brushType: 'stroke'
                },
                label: {
                    normal: {
                        show: true,
                        position: 'right',
                        formatter: '{b}'
                    }
                },
                symbolSize: function (val) {
                    return val[2] / 8;
                },
                itemStyle: {
                    normal: {
                        color: color[i]
                    }
                },
                data: item[1].map(function (dataItem) {
                    return {
                        name: dataItem[1].name,
                        value: geoCoordMap[dataItem[1].name].concat([dataItem[1].value])
                    };
                })
            });
    });


    // based ready dom, initialize echarts instance 
    var chart = echarts.init(document.getElementById('main'));

    option = {
        backgroundColor: '#404a59',
        title: {
            text: '流量地图',
            subtext: '数据来自 npcap 实时抓取',
            left: 'center',
            textStyle: {
                color: '#fff'
            }
        },
        tooltip: {
            trigger: 'item'
        },
        legend: {
            orient: 'vertical',
            top: 'bottom',
            left: 'right',
            data: ['Beijing Top10', 'Shanghai Top10', 'Canton Top10'],
            textStyle: {
                color: '#fff'
            },
            selectedMode: 'single'
        },
        geo: {
            map: 'china',
            label: {
                emphasis: {
                    show: false
                }
            },
            roam: true,
            itemStyle: {
                normal: {
                    areaColor: '#323c48',
                    borderColor: '#404a59'
                },
                emphasis: {
                    areaColor: '#2a333d'
                }
            }
        },
        series: series
    };

    // Use just the specified configurations and data charts. 
    chart.setOption(option);
});

实验其中一次的结果如下:

说明:图中的每一条路径上的箭头数量代表了流量包的数量。

习题与思考题

1、应用WINPCAP能实现哪些网络应用?

  • 捕获原始数据包。不管这个包是发往本地机,还是其他机器之间的交换包
  • 在数据包被发送到应用程序之前,通过用户定义的规则过滤
  • 向网络发送原始数据包
  • 对网络通信量做出统计

应用 winpcap 实现的典型应用程序如 WireShark。

项目源码地址

https://github.com/FanyFull/ComputerNetworkExperiment


参考:

1、https://www.reddit.com/r/ccna/comments/7mh86d/what_does_protocol_control_information_means/


计算机网络实验报告完整版
http://fanyfull.github.io/2022/06/29/计算机网络实验报告完整版/
作者
Fany Full
发布于
2022年6月29日
许可协议