linux内核协议栈之网络信息统计SNMP

本文介绍了SNMP简单网络管理协议的基本概念及其在Linux内核中的工作原理。SNMP通过管理站和代理之间的MIB对象进行通信,统计网络信息。内核通过proc系统展示协议统计,如/proc/net/snmp文件记录IPv4统计信息。分析了内核如何为SNMP提供统计信息的框架,包括net结构体中的mib成员、协议族初始化、统计函数的使用以及proc系统的数据展示。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

SNMP简介

SNMP信息统计框架和实现分析


SNMP简介

SNMP是英文"Simple Network Management Protocol"的缩写,中文意思是"简单网络管理协议"。SNMP是一种简单网络管理协议,它属于TCP/IP五层协议中的应用层协议,用于网络管理的协议。SNMP主要用于网络设备的管理。由于SNMP协议简单可靠 ,受到了众多厂商的欢迎,成为了目前最为广泛的网管协议。
SNMP的工作方式:管理员需要向设备获取数据,所以SNMP提供了【读】操作;管理员需要向设备执行设置操作,所以SNMP提供了【写】操作;设备需要在重要状况改变的时候,向管理员通报事件的发生,所以SNMP提供了【Trap】操作。

在具体实现上,SNMP为管理员提供了一个网管平台(NMS),又称为【管理站】,负责网管命令的发出、数据存储、及数据分析。【被】监管的设备上运行一个SNMP代理(Agent)),代理实现设备与管理站的SNMP通信。

管理站与代理端通过MIB进行接口统一,MIB定义了设备中的被管理对象。管理站和代理都实现了相应的MIB对象,使得双方可以识别对方的数据,实现通信。管理站向代理申请MIB中定义的数据,代理识别后,将管理设备提供的相关状态或参数等数据(这些数据由内核的相关实现提供,存储于/proc等位置,下图是/proc/net/snmp文件中的内容,记录了ipv4相关的统计信息)转换为MIB定义的格式,应答给管理站,完成一次管理操作。

在这里插入图片描述 内核网络协议对SNMP协议的支持主要体现在统计了相关的网络信息,并将这些信息通过proc系统或其他配置文件进行展示(本人项目只是简单涉及到这个协议,这是我对它的一个简单理解,如果有误欢迎指正、交流)。下面的内容就是介绍内核网络栈是如何提供这些统计信息的,它的实现框架是怎样的。

SNMP信息统计框架和实现分析

框架

在这里插入图片描述
具体实现
相关结构体和函数的代码展示在下方的代码块中,请自行查看。
1、首先在net结构中定义了一个netns_mib类型的mib成员,该成员中通过宏的方式定义了存放各协议统计信息的成员(是指针类型,所以需要初始化分配内存),它们的类型被定义在<include/net/snmp.h>中,大多数为一个数组,数组中存放各个统计信息,至于数组中什么位置存放什么信息则由定义在<include/uapi/linux/snmp.h>中的枚举类型定义,这些枚举类型都以0开始,表示数组的下标(见下方代码块)。

struct net {
	atomic_t		passive;	/* To decided when the network
						 * namespace should be freed.
						 */
	atomic_t		count;		/* To decided when the network
						 *  namespace should be shut down.
						 */
	spinlock_t		rules_mod_lock;

	u32			hash_mix;
	atomic64_t		cookie_gen;

	struct list_head	list;		/* list of network namespaces */
	struct list_head	cleanup_list;	/* namespaces on death row */
	struct list_head	exit_list;	/* Use only net_mutex */

	struct user_namespace   *user_ns;	/* Owning user namespace */
	spinlock_t		nsid_lock;
	struct idr		netns_ids;

	struct ns_common	ns;

	struct proc_dir_entry 	*proc_net;
	struct proc_dir_entry 	*proc_net_stat;

#ifdef CONFIG_SYSCTL
	struct ctl_table_set	sysctls;
#endif

	struct sock 		*rtnl;			/* rtnetlink socket */
	struct sock		*genl_sock;

	struct list_head 	dev_base_head;
	struct hlist_head 	*dev_name_head;
	struct hlist_head	*dev_index_head;
	unsigned int		dev_base_seq;	/* protected by rtnl_mutex */
	int			ifindex;
	unsigned int		dev_unreg_count;

	/* core fib_rules */
	struct list_head	rules_ops;


	struct net_device       *loopback_dev;          /* The loopback */
	struct netns_core	core;
	struct netns_mib	mib;    /* here*/
	struct netns_packet	packet;
	struct netns_unix	unx;
	struct netns_ipv4	ipv4;
#if IS_ENABLED(CONFIG_IPV6)
	struct netns_ipv6	ipv6;
#endif
......后面的代码省略
struct netns_mib {
	DEFINE_SNMP_STAT(struct tcp_mib, tcp_statistics);
	DEFINE_SNMP_STAT(struct ipstats_mib, ip_statistics);
	DEFINE_SNMP_STAT(struct linux_mib, net_statistics);
	DEFINE_SNMP_STAT(struct udp_mib, udp_statistics);
	DEFINE_SNMP_STAT(struct udp_mib, udplite_statistics);
	DEFINE_SNMP_STAT(struct icmp_mib, icmp_statistics);
	DEFINE_SNMP_STAT_ATOMIC(struct icmpmsg_mib, icmpmsg_statistics);

#if IS_ENABLED(CONFIG_IPV6)
	struct proc_dir_entry *proc_net_devsnmp6;
	DEFINE_SNMP_STAT(struct udp_mib, udp_stats_in6);
	DEFINE_SNMP_STAT(struct udp_mib, udplite_stats_in6);
	DEFINE_SNMP_STAT(struct ipstats_mib, ipv6_statistics);
	DEFINE_SNMP_STAT(struct icmpv6_mib, icmpv6_statistics);
	DEFINE_SNMP_STAT_ATOMIC(struct icmpv6msg_mib, icmpv6msg_statistics);
#endif
#ifdef CONFIG_XFRM_STATISTICS
	DEFINE_SNMP_STAT(struct linux_xfrm_mib, xfrm_statistics);
#endif
};
struct ipstats_mib {
	/* mibs[] must be first field of struct ipstats_mib */
	u64		mibs[IPSTATS_MIB_MAX];
	struct u64_stats_sync syncp;
};

/* ICMP */
#define ICMP_MIB_MAX	__ICMP_MIB_MAX
struct icmp_mib {
	unsigned long	mibs[ICMP_MIB_MAX];
};
       ......
enum
{
	TCP_MIB_NUM = 0,
	TCP_MIB_RTOALGORITHM,			/* RtoAlgorithm */
	TCP_MIB_RTOMIN,				/* RtoMin */
	TCP_MIB_RTOMAX,				/* RtoMax */
	TCP_MIB_MAXCONN,			/* MaxConn */
	TCP_MIB_ACTIVEOPENS,			/* ActiveOpens */
	TCP_MIB_PASSIVEOPENS,			/* PassiveOpens */
	TCP_MIB_ATTEMPTFAILS,			/* AttemptFails */
	TCP_MIB_ESTABRESETS,			/* EstabResets */
	TCP_MIB_CURRESTAB,			/* CurrEstab */
	TCP_MIB_INSEGS,				/* InSegs */
	TCP_MIB_OUTSEGS,			/* OutSegs */
	TCP_MIB_RETRANSSEGS,			/* RetransSegs */
	TCP_MIB_INERRS,				/* InErrs */
	TCP_MIB_OUTRSTS,			/* OutRsts */
	TCP_MIB_CSUMERRORS,			/* InCsumErrors */
	__TCP_MIB_MAX
};

/* udp mib definitions */
/*
 * RFC 1213:  MIB-II UDP group
 * RFC 2013 (updates 1213):  SNMPv2-MIB-UDP
 */
enum
{
	UDP_MIB_NUM = 0,
	UDP_MIB_INDATAGRAMS,			/* InDatagrams */
	UDP_MIB_NOPORTS,			/* NoPorts */
	UDP_MIB_INERRORS,			/* InErrors */
	UDP_MIB_OUTDATAGRAMS,			/* OutDatagrams */
	UDP_MIB_RCVBUFERRORS,			/* RcvbufErrors */
	UDP_MIB_SNDBUFERRORS,			/* SndbufErrors */
	UDP_MIB_CSUMERRORS,			/* InCsumErrors */
	UDP_MIB_IGNOREDMULTI,			/* IgnoredMulti */
	__UDP_MIB_MAX
};
            ......

2、协议族在初始化时为这些统计结构分配空间,进行初始化,这里初始化用的是per_cpu变量,即在每个cpu中都分配一个该变量的副本,这样做的目的是为了减少同步机制带来的性能损耗(在多处理器环境下避免了在修改此值时的加锁操作,通过空间换时间)。per_cpu相关

static __net_init int ipv4_mib_init_net(struct net *net)
{
	int i;

	net->mib.tcp_statistics = alloc_percpu(struct tcp_mib);
	if (!net->mib.tcp_statistics)
		goto err_tcp_mib;
	net->mib.ip_statistics = alloc_percpu(struct ipstats_mib);
	if (!net->mib.ip_statistics)
		goto err_ip_mib;

	for_each_possible_cpu(i) {
		struct ipstats_mib *af_inet_stats;
		af_inet_stats = per_cpu_ptr(net->mib.ip_statistics, i);
		u64_stats_init(&af_inet_stats->syncp);
	}

	net->mib.net_statistics = alloc_percpu(struct linux_mib);
	if (!net->mib.net_statistics)
		goto err_net_mib;
	net->mib.udp_statistics = alloc_percpu(struct udp_mib);
	if (!net->mib.udp_statistics)
		goto err_udp_mib;
	net->mib.udplite_statistics = alloc_percpu(struct udp_mib);
	if (!net->mib.udplite_statistics)
		goto err_udplite_mib;
	net->mib.icmp_statistics = alloc_percpu(struct icmp_mib);
	if (!net->mib.icmp_statistics)
		goto err_icmp_mib;
	net->mib.icmpmsg_statistics = kzalloc(sizeof(struct icmpmsg_mib),
					      GFP_KERNEL);
	if (!net->mib.icmpmsg_statistics)
		goto err_icmpmsg_mib;

	tcp_mib_init(net);
	return 0;

err_icmpmsg_mib:
	free_percpu(net->mib.icmp_statistics);
err_icmp_mib:
	free_percpu(net->mib.udplite_statistics);
err_udplite_mib:
	free_percpu(net->mib.udp_statistics);
err_udp_mib:
	free_percpu(net->mib.net_statistics);
err_net_mib:
	free_percpu(net->mib.ip_statistics);
err_ip_mib:
	free_percpu(net->mib.tcp_statistics);
err_tcp_mib:
	return -ENOMEM;
}

static __net_exit void ipv4_mib_exit_net(struct net *net)
{
	kfree(net->mib.icmpmsg_statistics);
	free_percpu(net->mib.icmp_statistics);
	free_percpu(net->mib.udplite_statistics);
	free_percpu(net->mib.udp_statistics);
	free_percpu(net->mib.net_statistics);
	free_percpu(net->mib.ip_statistics);
	free_percpu(net->mib.tcp_statistics);
}

static __net_initdata struct pernet_operations ipv4_mib_ops = {
	.init = ipv4_mib_init_net,
	.exit = ipv4_mib_exit_net,
};

static int __init init_ipv4_mibs(void)
{
	return register_pernet_subsys(&ipv4_mib_ops);
}

3、內镶在网络栈中的统计函数在对应的位置进行统计,将信息写入上述的统计字段(统计到当前cpu的副本中)。比如下方udp_recvmsg函数中的UDP_INC_STATS_USER(sock_net(sk), UDP_MIB_CSUMERRORS, is_udplite) 和
UDP_INC_STATS_USER(sock_net(sk), UDP_MIB_INERRORS, is_udplite) 就是在统计校验和错误的数据包以及接收出错的数据包
这些宏其实就是增加对应统计字段的值。

int udp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int noblock,
		int flags, int *addr_len)
{
	struct inet_sock *inet = inet_sk(sk);
	DECLARE_SOCKADDR(struct sockaddr_in *, sin, msg->msg_name);
	struct sk_buff *skb;
	unsigned int ulen, copied;
	int peeked, off = 0;
	int err;
	int is_udplite = IS_UDPLITE(sk);
	bool checksum_valid = false;
	bool slow;

	if (flags & MSG_ERRQUEUE)
		return ip_recv_error(sk, msg, len, addr_len);

try_again:
	skb = __skb_recv_datagram(sk, flags | (noblock ? MSG_DONTWAIT : 0),
				  &peeked, &off, &err);
	if (!skb)
		goto out;

	ulen = skb->len - sizeof(struct udphdr);
	copied = len;
	if (copied > ulen)
		copied = ulen;
	else if (copied < ulen)
		msg->msg_flags |= MSG_TRUNC;

	/*
	 * If checksum is needed at all, try to do it while copying the
	 * data.  If the data is truncated, or if we only want a partial
	 * coverage checksum (UDP-Lite), do it before the copy.
	 */

	if (copied < ulen || UDP_SKB_CB(skb)->partial_cov) {
		checksum_valid = !udp_lib_checksum_complete(skb);
		if (!checksum_valid)
			goto csum_copy_err;
	}

	if (checksum_valid || skb_csum_unnecessary(skb))
		err = skb_copy_datagram_msg(skb, sizeof(struct udphdr),
					    msg, copied);
	else {
		err = skb_copy_and_csum_datagram_msg(skb, sizeof(struct udphdr),
						     msg);

		if (err == -EINVAL)
			goto csum_copy_err;
	}

	if (unlikely(err)) {
		trace_kfree_skb(skb, udp_recvmsg);
		if (!peeked) {
			atomic_inc(&sk->sk_drops);
			UDP_INC_STATS_USER(sock_net(sk),
					   UDP_MIB_INERRORS, is_udplite);
		}
		goto out_free;
	}

	if (!peeked)
		UDP_INC_STATS_USER(sock_net(sk),
				UDP_MIB_INDATAGRAMS, is_udplite);

	sock_recv_ts_and_drops(msg, sk, skb);

	/* Copy the address. */
	if (sin) {
		sin->sin_family = AF_INET;
		sin->sin_port = udp_hdr(skb)->source;
		sin->sin_addr.s_addr = ip_hdr(skb)->saddr;
		memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
		*addr_len = sizeof(*sin);
	}
	if (inet->cmsg_flags)
		ip_cmsg_recv_offset(msg, skb, sizeof(struct udphdr), off);

	err = copied;
	if (flags & MSG_TRUNC)
		err = ulen;

out_free:
	skb_free_datagram_locked(sk, skb);
out:
	return err;

csum_copy_err:
	slow = lock_sock_fast(sk);
	if (!skb_kill_datagram(sk, skb, flags)) {
		UDP_INC_STATS_USER(sock_net(sk), UDP_MIB_CSUMERRORS, is_udplite);
		UDP_INC_STATS_USER(sock_net(sk), UDP_MIB_INERRORS, is_udplite);
	}
	unlock_sock_fast(sk, slow);

	/* starting over for a new packet, but check if we need to yield */
	cond_resched();
	msg->msg_flags &= ~MSG_TRUNC;
	goto try_again;
}

4、将这些数据通过proc系统(虚拟文件系统,存在于内存中)向用户提供访问。这时需要将对应字段在所有cpu中的副本加起来,这个工作在ipv4中是通过以下函数完成的。

unsigned long snmp_fold_field(void __percpu *mib, int offt)
{
	unsigned long res = 0;
	int i;

	for_each_possible_cpu(i)
		res += snmp_get_cpu_field(mib, i, offt);
	return res;
}

所有统计信息都被通过 /proc/net/snmp 文件展示给用户。
这部分的实现在对应协议族下的 proc.c 文件中,将上述统计信息按照特定格式展示在proc文件中。

static int snmp_seq_show(struct seq_file *seq, void *v)
{
	int i;
	struct net *net = seq->private;

	seq_puts(seq, "Ip: Forwarding DefaultTTL");

	for (i = 0; snmp4_ipstats_list[i].name != NULL; i++)
		seq_printf(seq, " %s", snmp4_ipstats_list[i].name);

	seq_printf(seq, "\nIp: %d %d",
		   IPV4_DEVCONF_ALL(net, FORWARDING) ? 1 : 2,
		   sysctl_ip_default_ttl);

	BUILD_BUG_ON(offsetof(struct ipstats_mib, mibs) != 0);
	for (i = 0; snmp4_ipstats_list[i].name != NULL; i++)
		seq_printf(seq, " %llu",
			   snmp_fold_field64(net->mib.ip_statistics,
					     snmp4_ipstats_list[i].entry,
					     offsetof(struct ipstats_mib, syncp)));

	icmp_put(seq);	/* RFC 2011 compatibility */
	icmpmsg_put(seq);

	seq_puts(seq, "\nTcp:");
	for (i = 0; snmp4_tcp_list[i].name != NULL; i++)
		seq_printf(seq, " %s", snmp4_tcp_list[i].name);

	seq_puts(seq, "\nTcp:");
	for (i = 0; snmp4_tcp_list[i].name != NULL; i++) {
		/* MaxConn field is signed, RFC 2012 */
		if (snmp4_tcp_list[i].entry == TCP_MIB_MAXCONN)
			seq_printf(seq, " %ld",
				   snmp_fold_field(net->mib.tcp_statistics,
						   snmp4_tcp_list[i].entry));
		else
			seq_printf(seq, " %lu",
				   snmp_fold_field(net->mib.tcp_statistics,
						   snmp4_tcp_list[i].entry));
	}

	seq_puts(seq, "\nUdp:");
	for (i = 0; snmp4_udp_list[i].name != NULL; i++)
		seq_printf(seq, " %s", snmp4_udp_list[i].name);

	seq_puts(seq, "\nUdp:");
	for (i = 0; snmp4_udp_list[i].name != NULL; i++)
		seq_printf(seq, " %lu",
			   snmp_fold_field(net->mib.udp_statistics,
					   snmp4_udp_list[i].entry));

	/* the UDP and UDP-Lite MIBs are the same */
	seq_puts(seq, "\nUdpLite:");
	for (i = 0; snmp4_udp_list[i].name != NULL; i++)
		seq_printf(seq, " %s", snmp4_udp_list[i].name);

	seq_puts(seq, "\nUdpLite:");
	for (i = 0; snmp4_udp_list[i].name != NULL; i++)
		seq_printf(seq, " %lu",
			   snmp_fold_field(net->mib.udplite_statistics,
					   snmp4_udp_list[i].entry));

	seq_putc(seq, '\n');
	return 0;
}

static __net_init int ip_proc_init_net(struct net *net)
{
	if (!proc_create("sockstat", S_IRUGO, net->proc_net,
			 &sockstat_seq_fops))
		goto out_sockstat;
	if (!proc_create("netstat", S_IRUGO, net->proc_net, &netstat_seq_fops))
		goto out_netstat;
	if (!proc_create("snmp", S_IRUGO, net->proc_net, &snmp_seq_fops))
		goto out_snmp;

	return 0;

out_snmp:
	remove_proc_entry("netstat", net->proc_net);
out_netstat:
	remove_proc_entry("sockstat", net->proc_net);
out_sockstat:
	return -ENOMEM;
}

本文只是简单对内核网络栈对snmp的支持进行了分析,总的来说,这部分内容还是比较独立的,在开发自己的协议栈时,如果要增加这部分内容,那么你所需要做的并不复杂,唯一比较麻烦的地方可能就在于在增加了自己定义的内容后需要重新编译内核,因为你改动了net结构体!这个结构体是所有内核网络协议栈共用的!
OVER
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值