Linux下C/C++实现(网络流量分析-NTA)

网络流量分析(NTA - Network Traffic Analysis) 就是捕捉网络中流动的数据包,并通过查看包内部数据以及进行相关的协议、流量、分析、统计等,协助发现网络运行过程中出现的问题。通过监控和分析网络环境中的流量,来判断流量是用在哪个系统或者哪个应用,并对流量做相应的分析统计。

网络流量分析解决方案用于监控企业网络。公司利用网络流量分析来记录和分析网络流量和资产间通信的模式。这些数据随后被用于发现和应对安全威胁。网络流量分析跟踪网络可用性和活动,并识别操作和安全方面的违规行为。

网络流量类型

为了更好地管理带宽,网络管理员决定路由器和交换机等网络设备如何处理某些类型的流量。网络流量一般分为两类:实时流量和非实时流量。

实时流量:

被视为对业务运营重要或关键的流量必须以尽可能高的质量按时交付。实时网络流量的示例包括VoIP、视频会议和网络浏览。

非实时流量:

也称为尽力而为流量,是网络管理员认为不如实时流量重要的流量。示例包括用于网络发布和电子邮件应用程序的文件传输协议(FTP)。

为什么网络流量分析和监控很重要

密切关注你的网络周边一种很好的做法。即使有强大的防火墙,错误也可能发生,恶意流量也可能通过。用户还可以利用隧道、外部匿名器和VPN等方法绕过防火墙规则。

此外,近年来勒索软件作为一种常见攻击类型的兴起,使得网络流量监控变得更加重要。

远程桌面协议(RDP)是另一个常见的目标应用程序。请确保阻止防火墙上的任何入站连接尝试。监控防火墙内的流量可以使您验证规则,获得有价值的见解,还可以用作基于网络流量的警报来源。

注意与Telnet等管理协议相关的任何可疑活动。由于Telnet是一种未加密的协议,会话流量将显示适用于设备品牌和型号的命令行界面(CLI)命令序列。CLI字符串可能会显示登录过程、用户凭据的显示、显示启动或运行配置的命令、复制文件等。请确保检查网络数据中是否有任何运行未加密管理协议的设备,例如:

1.Telnet(远程登录)
2.超文本传输协议(HTTP,端口80)
3.简单网络管理协议(SNMP,端口161/162)

分析和监控网络流量的目的是什么?

通过在网络边缘和网络核心实现网络流量分析,可以研究许多操作和安全问题。使用流量分析工具,您可以发现大量下载、流媒体或可疑的入站或出站流量。确保您从监控防火墙的内部接口开始,这将允许您跟踪特定客户端或用户的活动。

NTA还为组织提供了在端点之外对其网络上的威胁的更多可见性。随着移动设备、物联网设备、智能电视等的兴起,你需要的不仅仅是来自防火墙的日志,而是更智能的东西。当网络受到攻击时,防火墙日志也会出现问题。您可能会发现,由于防火墙上的资源负载,它们无法访问,或者它们被覆盖(有时甚至被黑客修改),导致重要的取证信息丢失。

Linux监控网络流量分析

Linux 内核提供了一种通过 /proc 文件系统,在运行时访问内核内部数据结构、改变内核设置的机制。proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为访问系统内核数据的操作提供接口。

用户和应用程序可以通过proc得到系统的信息,并可以改变内核的某些参数。由于系统的信息,如进程,是动态改变的,所以用户或应用程序读取proc文件时,proc文件系统是动态从系统内核读出所需信息并提交的。/proc文件系统中包含了很多目录,其中/proc/net/dev 保存了网络适配器及统计信息。

  • /proc/net/dev


bytes: 接口发送或接收的数据的总字节数
packets: 接口发送或接收的数据包总数
errs: 由设备驱动程序检测到的发送或接收错误的总数
drop: 设备驱动程序丢弃的数据包总数
fifo: FIFO缓冲区错误的数量
frame: 分组帧错误的数量
colls: 接口上检测到的冲突数
compressed: 设备驱动程序发送或接收的压缩数据包数
carrier: 由设备驱动程序检测到的载波损耗的数量
multicast: 设备驱动程序发送或接收的多播帧数

  • /proc/net/snmp

简单网络管理协议(SNMP)是一种网络协议,用于管理和监控互联网协议网络中的网络连接设备。提供了主机各层的IP、ICMP、ICMPMsg、TCP、UDP详细数据

  • /proc/net/netstat

/proc/net/netstat 提供了主机的收发包数、收包字节数据。

在查看centos机器的网络状态时,尝尝要看看计数器文件/proc/net/netstat,但是直接cat,如下显示

上面这个太难看懂,有如下awk脚本,可以格式化

cat /proc/net/netstat |  awk '(f==0) {name=$1; i=2; while ( i<=NF) {n[i] = $i; i++ }; f=1; next} (f==1){ i=2; while ( i<=NF){ printf "%s%s = %d\n", name, n[i], $i; i++}; f=0} ' 


Linux下C/C++实现(网络流量分析-NTA)


static int split_ifname(char *if_name, char *drv, uint32_t *instance)
{
	char *p;
	int n, m;

	n = 0;
	for (p = if_name; *p; p++)
		n++;
	if (n <= 1)
		return (B_FALSE);
	m = n;
	for (p--; isdigit(*p); p--)
		n--;
	if (m == n || n == 0)
		return (B_FALSE);
	(void) strncpy(drv, if_name, n);
	drv[n] = '\0';
	*instance = (uint32_t)atol(++p);
	return (B_TRUE);
}

static void diag(int use_errno, char *format, ...)
{
	va_list ap;
	char *error_str;

	(void) fprintf(stderr, "%s: ", g_progname);
	if (use_errno) 
	{
		error_str = strerror(errno);
		if (! error_str)
			error_str = strerror(0);
	}
	va_start(ap, format);
	(void) vfprintf(stderr, format, ap);
	va_end(ap);
	if (use_errno)
		(void) fprintf(stderr, ": %s\n", error_str);
	else
		(void) fputc('\n', stderr);
}

#define	die(...) \
do { \
	diag(__VA_ARGS__);			\
	exit(2);				\
} while (0)

static void update_stats(int net_dev)
{


...
	if (lseek(net_dev, 0, SEEK_SET) != 0)
		die(1, "lseek: %s", PROC_NET_DEV_PATH);
	bufsiz = 0;
	while ((n = read(net_dev, (void *) proc_net_buffer + bufsiz, sizeof(proc_net_buffer) - bufsiz)) > 0) 
	{
	    bufsiz += n;
	}
	if (bufsiz < 0)
		die(1, "read: %s", PROC_NET_DEV_PATH);
	else if (bufsiz < 200)
		die(0, "%s: invalid format\n", PROC_NET_DEV_PATH);

...

	/* 终止我们的字符串 */
	bufp = proc_net_buffer + 200;
	buf_remain = bufsiz - 200;
	bufp[buf_remain + 1] = '\0';

	gettimeofday(&now_tv, NULL);

	skip_to_newline = 0;
	g_nicdata_count = 0;
	lastp = NULL;
	while (*bufp) 
	{
		if (skip_to_newline) 
		{
			/* 需要跳过以前的数据 */
			for (; *bufp; bufp++)
				if (*bufp == '\n') 
				{
					bufp++;
					break;
			}
			if (! *bufp)
				break;
		}
		skip_to_newline = 1;

		/* 获取接口名称 */
		while (*bufp == ' ')
			bufp++;
		/* 检查格式 */
		n = strcspn(bufp, ":");
		if (n >= sizeof (if_name))
			die(0, "%s: interface name too long",
				PROC_NET_DEV_PATH);
		(void) strncpy(if_name, bufp, n);
		if_name[n] = '\0';
...
		loopback = streql("lo", if_name);
		if (g_nonlocal && loopback)
			continue;

		bufp += n + 1;
		ret = sscanf(bufp, "%llu %llu %llu %llu %llu %llu %llu"
			" %llu %llu %llu %llu %llu %llu %llu %llu %llu\n",
			&ll[0], &ll[1], &ll[2], &ll[3], &ll[4], &ll[5],
			&ll[6], &ll[7], &ll[8], &ll[9], &ll[10], &ll[11],
			&ll[12], &ll[13], &ll[14], &ll[15]);
		if (ret != 16)
			die(0, "%s: invalid format", PROC_NET_DEV_PATH);
		/* 如果从未看到数据包,则跳过接口 */
		if (ll[1] == 0 && ll[9] == 0)
			continue;

		g_nicdata_count++;
		nicp = find_nicdatap(&g_nicdatap, &lastp, if_name);
		nicp->new.tv.tv_sec = now_tv.tv_sec;
		nicp->new.tv.tv_usec = now_tv.tv_usec;
		nicp->new.rbytes = ll[0];
		nicp->new.rpackets = ll[1];
		nicp->new.wbytes = ll[8];
		nicp->new.wpackets = ll[9];
		nicp->new.sat = ll[2];
		nicp->new.sat += ll[3];
		nicp->new.sat += ll[11];
		nicp->new.sat += ll[12];
		nicp->new.sat += ll[13];
		nicp->new.sat += ll[14];
		if (g_opt_x) {
			nicp->new.ierr = ll[2];
			nicp->new.oerr = ll[10];
			nicp->new.coll = ll[13];
		}
		if (loopback)
			nicp->flags |= NIC_LOOPBACK;
		get_speed_duplex(nicp);
		nicp->report = 1;
	}
	if (g_tcp || g_udp)
		load_snmp(g_snmp);
	if (g_tcp) 
	{
		g_tcp_new->tv = now_tv;
		load_netstat(g_netstat);
	}
	if (g_udp)
		g_udp_new->tv = now_tv;
}

...

static void load_netstat(FILE *netstat)
{
...

	if (fseek(netstat, 0, SEEK_SET) != 0)
		die(1, "fseek: %s", PROC_NET_NETSTAT_PATH);
	remaining = 1;
	while (remaining) 
	{
		p = fgets(buf, sizeof (buf), netstat);
		if (! p)
			break;
		if (g_tcp && strncmp("TcpExt: SyncookiesSent SyncookiesRecv "
		    "SyncookiesFailed EmbryonicRsts PruneCalled RcvPruned "
		    "OfoPruned OutOfWindowIcmps LockDroppedIcmps "
		    "ArpFilter TW TWRecycled TWKilled PAWSPassive "
		    "PAWSActive PAWSEstab DelayedACKs DelayedACKLocked "
		    "DelayedACKLost ListenOverflows ListenDrops ",
		    p, 273) == 0) 
		  {
			/* We are after field 20 and 21 */
			int n = fscanf(netstat, "TcpExt: %*d %*d %*d "
				"%*d %*d %*d %*d %*d %*d %*d "
				"%*d %*d %*d %*d %*d %*d %*d "
				"%*d %*d %lld %lld ",
				&ll[0], &ll[1]);
			if (n == 2)
				g_tcp_new->listenDrop = ll[0] + ll[1];
			remaining--;
		}
	}
}


/*
获取TCP和UDP统计信息
*/

static void load_snmp(FILE *snmp)
{
..

	/* 从/proc/net/snmp加载TCP和/或UDP统计信息 */
	if (fseek(snmp, 0, SEEK_SET) != 0)
		die(1, "fseek: %s", PROC_NET_SNMP_PATH);
	remaining = 0;
	if (g_tcp)
		remaining++;
	if (g_udp)
		remaining++;
	while (remaining) 
	{
		p = fgets(buf, sizeof (buf), snmp);
		if (! p)
			break;
		if (g_tcp && strncmp("Tcp: RtoAlgorithm RtoMin RtoMax MaxConn "
				"ActiveOpens PassiveOpens AttemptFails "
				"EstabResets CurrEstab InSegs OutSegs "
				"RetransSegs InErrs OutRsts", p, 141) == 0) 
				{
			int n;
			n = fscanf(snmp, "Tcp: %lld %lld %lld %lld "
			    "%lld %lld %lld %lld %lld %lld "
			    "%lld %lld %lld %lld\n",
			    &ll[0], &ll[1], &ll[2], &ll[3],
			    &ll[4], &ll[5], &ll[6], &ll[7],
			    &ll[8], &ll[9], &ll[10], &ll[11],
			    &ll[12], &ll[13]);
			if (n == 14) 
			{
				g_tcp_new->inDataInorderSegs = ll[9];
				g_tcp_new->outDataSegs = ll[10];
				g_tcp_new->estabResets = ll[7];
				g_tcp_new->outRsts = ll[13];
				g_tcp_new->attemptFails = ll[6];
				...
				g_tcp_new->retransBytes = ll[11];
				g_tcp_new->passiveOpens = ll[5];
				g_tcp_new->activeOpens = ll[4];
			}
			remaining--;
		} 
		else if (g_udp && strncmp("Udp: InDatagrams NoPorts "
				"InErrors OutDatagrams RcvbufErrors "
				"SndbufErrors\n", p, 72) == 0) {
			int n;
			n = fscanf(snmp, "Udp: %lld %lld %lld %lld "
			    "%lld %lld\n",
			    &ll[0], &ll[1], &ll[2], &ll[3],
			    &ll[4], &ll[5]);
			if (n == 6) 
			{
				g_udp_new->inDatagrams = ll[0];
				g_udp_new->outDatagrams = ll[3];
				g_udp_new->inErrors = ll[2]; /* + ll[4]? */
				g_udp_new->outErrors = ll[5];
			}
			remaining--;
		}
	}
}

static void print_header(void)
{
...
	switch (g_style) 
	{
	case STYLE_SUMMARY:
		(void) printf("%8s %8s %14s %14s\n",
		    "Time", "Int", g_runit_1, g_wunit_1);
		break;
	case STYLE_FULL:
		(void) printf("%8s %8s %7s %7s %7s "
		    "%7s %7s %7s %5s %6s\n",
		    "Time", "Int", g_runit_1, g_wunit_1, "rPk/s",
		    "wPk/s", "rAvs", "wAvs", "%Util", "Sat");
		break;
	case STYLE_FULL_UTIL:
		(void) printf("%8s %8s %7s %7s %7s "
		    "%7s %7s %7s %6s %6s\n",
		    "Time", "Int", g_runit_1, g_wunit_1, "rPk/s",
		    "wPk/s", "rAvs", "wAvs", "%rUtil", "%wUtil");
		break;
	case STYLE_EXTENDED:
		update_timestr(NULL);
		(void) printf("%-10s %7s %7s %7s %7s  "
		    "%5s %5s %5s %5s %5s  %5s\n",
		    g_timestr, g_runit_2, g_wunit_2, "RdPkt", "WrPkt",
		    "IErr", "OErr", "Coll", "NoCP", "Defer", "%Util");
		break;
	case STYLE_EXTENDED_UTIL:
		update_timestr(NULL);
		(void) printf("%-10s %7s %7s %7s %7s  "
		    "%5s %5s %5s %5s %5s %6s %6s\n",
		    g_timestr, g_runit_2, g_wunit_2, "RdPkt", "WrPkt",
		    "IErr", "OErr", "Coll", "NoCP", "Defer",
		    "%rUtil", "%wUtil");
		break;
	}
}

...

运行结果:

Time列:表示当前采样的响应时间.
lo and ens37: 网卡名称.
rKB/s : 每秒接收到千字节数.
wKB/s : 每秒写的千字节数.
rPk/s : 每秒接收到的数据包数目.
wPk/s : 每秒写的数据包数目.
rAvs : 接收到的数据包平均大小.
wAvs : 传输的数据包平均大小.
%Util : 网卡利用率(百分比).
Sat : 网卡每秒的错误数.网卡是否接近饱满的一个指标.尝试去诊断网络问题的时候,推荐使用-x选项去查看更多的统计信息.

  • 查看tcp相关信息

    InKB : 表示每秒接收到的千字节.
    OutKB : 表示每秒传输的千字节.
    InSeg : 表示每秒接收到的TCP数据段(TCP Segments).
    OutSeg : 表示每秒传输的TCP数据段(TCP Segments).
    Reset : 表示TCP连接从ESTABLISHED或CLOSE-WAIT状态直接转变为CLOSED状态的次数.
    AttF : 表示TCP连接从SYN-SENT或SYN-RCVD状态直接转变为CLOSED状态的次数,再加上TCP连接从SYN-RCVD状态直接转变为LISTEN状态的次数
    %ReTX : 表示TCP数据段(TCP Segments)重传的百分比.即传输的TCP数据段包含有一个或多个之前传输的八位字节.
    InConn : 表示TCP连接从LISTEN状态直接转变为SYN-RCVD状态的次数.
    OutCon : 表示TCP连接从CLOSED状态直接转变为SYN-SENT状态的次数.
    Drops : 表示从完成连接(completed connection)的队列和未完成连接(incomplete connection)的队列中丢弃的连接次数.

  • 查看udp相关信息

    InDG : 每秒接收到的UDP数据报(UDP Datagrams)
    OutDG : 每秒传输的UDP数据报(UDP Datagrams)
    InErr : 接收到的因包含错误而不能被处理的数据包
    OutErr :因错误而不能成功传输的数据包.

  • 查看网卡速度

If you need the complete source code, please add the WeChat number (c17865354792)

总结

网络流量分析解决方案领先于其他网络安全工具,如入侵检测系统(IDS)、入侵防御系统(IPS)和防火墙。虽然这些工具主要关注网络周边的流量,但网络流量分析分析所有类型的网络通信,包括传统的TCP/IP数据包、云流量、虚拟网络流量以及API和SaaS交互。

Welcome to follow WeChat official account【程序猿编码

参考:https://man7.org/linux/man-pages/man5/proc.5.html

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值