Linux C/C++ 原始套接字:打造链路层ping实现

news2024/11/27 8:40:05

在C/C++中,我们可以使用socket函数来创建套接字。我们需要指定地址族为AF_PACKET,协议为htons(ETH_P_ALL)来捕获所有传入和传出的数据包。

可以使用sendto和recvfrom函数来发送和接收数据包。我们需要构建一个合法的链路层数据包,在数据包的头部添加目标MAC地址和源MAC地址,并指定以太网类型为htons(ETH_P_IP)。

然后,可以在发送前设置IP头部和ICMP头部。为了构建一个有效的ICMP Echo Request消息,需要正确设置ICMP类型、代码、校验和和标识符字段。

最后,通过接收数据包并解析来获取目标设备的响应。需要注意处理ICMP Echo Reply消息,并检查序列号和校验和的正确性。

原始套接字的概念

原始套接字(Raw Socket)是一种在网络编程中使用的特殊套接字类型,它允许应用程序直接访问和操作网络协议栈中的网络层和传输层协议。与其他套接字类型(如TCP套接字和UDP套接字)相比,原始套接字提供更底层的网络访问能力。

原始套接字允许应用程序发送和接收自定义的网络数据包,绕过操作系统的网络协议栈的上层处理。应用程序可以直接构建和解析网络协议的头部,并将数据发送到特定的目标主机或接口。这种直接的访问能力使得原始套接字成为一种强大的工具,能够实现各种高级网络功能和协议,如网络监测、网络仿真、路由协议实现等。

使用原始套接字时,应用程序需要具有足够的权限来使用它,通常需要以超级用户(如root用户)的身份运行。这是因为原始套接字具有更高的网络访问权限,可以直接操作底层网络接口和协议,不受操作系统的保护和限制。

原始套接字的使用需要对网络协议和协议栈的内部工作原理有一定的了解,以正确构建和解析网络数据包的格式。它通常用于开发高级网络应用程序、网络调试工具、网络安全评估工具等场景中,对网络进行更底层的控制和操作。而在普通的网络编程中,一般使用更高级的套接字类型(如TCP套接字和UDP套接字)来实现应用层协议的交互。

链路层ping实现原理

链路层ping工具使用原始套接字实现,涉及以下原理:

  1. 原始套接字:原始套接字是一种可以直接访问网络协议栈的套接字类型。它允许应用程序直接发送和接收网络数据包,绕过传输层和应用层协议的处理。

  2. ICMP协议:链路层ping工具使用的是ICMP协议(Internet Control Message Protocol)。ICMP协议是在网络层之上使用的一种控制协议,用于在IP网络中传递错误消息和有关网络状态的信息。

  3. ICMP Echo请求和响应:链路层ping工具通常发送ICMP Echo请求(也称为ping请求)到目标主机,并期望收到相应的ICMP Echo响应。当目标主机收到Echo请求时,它将生成一个对应的Echo响应并将其发送回源主机。

  4. 构造和解析ICMP数据包:链路层ping工具需要构造符合ICMP协议规范的数据包,并能够解析收到的ICMP响应数据包。ICMP数据包包含ICMP报文头部和负载数据,其中ICMP Echo请求和响应的报文类型为8和0。

  5. IP数据包发送和接收:链路层ping工具使用原始套接字发送和接收IP数据包。在构造ICMP数据包时,会将ICMP报文作为负载放入IP数据报中,并设置目标IP地址、源IP地址、协议类型等IP头部字段。

  6. 校验和计算:在构造ICMP数据包时,需要计算校验和字段。校验和计算是为了确保数据在传输过程中的完整性,接收端可以通过校验和验证数据包的正确性。

  7. Root权限:由于原始套接字需要对网络接口进行底层访问,因此使用原始套接字通常需要root权限。

链路层ping的需求

链路层ping工具的需求可以归纳为以下几个方面:

  1. 网络连通性测试:链路层ping工具可以用于测试两个网络节点之间的连通性。它能够发送ICMP Echo请求到目标节点,并获取对应的ICMP Echo响应,从而确定节点之间是否能够正常通信。

  2. 网络性能测试:链路层ping工具可以测量网络链路的性能指标,如往返延迟(RTT,Round-Trip Time)和丢包率。通过发送ICMP Echo请求,并测量请求到响应的时间差,可以估计网络链路的延迟。同时,通过记录响应丢失的数量,可以计算丢包率。

  3. 网络故障排除:当网络出现故障时,链路层ping工具可以用于排查故障原因。通过在网络各个节点之间进行ping测试,可以确定具体哪个节点之间出现问题,从而有针对性地进行故障排除。

  4. 确定网络拓扑:通过链路层ping工具,在网络中的不同节点之间进行ping测试,可以帮助确定网络的拓扑结构,包括节点之间的连接方式和路径。

  5. 定位网络性能瓶颈:链路层ping工具可以用于定位网络中的性能瓶颈。通过在不同节点之间进行ping测试,并分析各个测试点的延迟和丢包率,可以确定网络的性能瓶颈所在,从而采取相应的优化措施。

  6. 监控网络稳定性:链路层ping工具可以用于监测网络的稳定性。通过定期进行ping测试,并记录网络的性能指标,可以获得网络的运行情况,并及时发现潜在的问题。

  7. 自动化网络管理:链路层ping工具可以通过脚本或自动化程序进行批量测试和监控,从而实现对大规模网络的管理和优化。

Linux C/C++ 原始套接字:打造链路层ping工具

使用原始套接字(AF_PACKET、SOCK_raw)实现的链接层ping实用程序.

struct ping_config {
	unsigned long count;
	unsigned long size;
	unsigned long interval;	/* in milliseconds */
	int listen;
	const char *ifname;
	int ifindex;
	unsigned char ifaddr[ETH_ALEN];
	struct list_head hosts;
	unsigned char replyto[ETH_ALEN];
};

struct ping_host {
	unsigned char host[ETH_ALEN];
	struct list_head node;
};


enum llcmp_types {
	LLCMP_ECHO_REQUEST = 128,
	LLCMP_ECHO_REPLY = 129,
};

/* 类似于ICMPv6回显请求/回复的结构 */
struct ping_header {
	struct ethhdr ethhdr;
	uint8_t reserved1;	
	uint8_t reserved2;	
	__be16 payload_len;	
	uint8_t type;
	uint8_t reserved3;	
	uint16_t reserved4;	
	__be16 identifier;
	__be16 seqno;
	uint8_t replyto[6];
} __attribute__((packed));

struct ping_config ping_config;

...


static void sigint_handler(int signo) {
	term = 1;
}

static int init_socket(void)
{
	struct ifreq ifreq;
	const char *ifname =  ping_config.ifname;
	int ret;
	struct epoll_event event;

	/* create socket */
	sd = socket(AF_PACKET, SOCK_RAW, htons(LLCMP_ETHER_TYPE));
	if (sd < 0) {
		fprintf(stderr,
			"Error: Can't open a raw socket for ether type 0x%04x\n",
			LLCMP_ETHER_TYPE);
		return -EPERM;
	}

	/* bind socket to specific interface */
	ret = setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, ifname,
			 strlen(ifname));
	if (ret < 0) {
		fprintf(stderr, "Error: Can't bind to device %s\n", ifname);
		goto err;
	}

	/* get MAC address and index of interface */
	memset(&ifreq, 0, sizeof(ifreq));
	strcpy(ifreq.ifr_name, ifname);

	ret = ioctl(sd, SIOCGIFHWADDR, &ifreq);
	if (ret < 0) {
		fprintf(stderr, "Error: Can't get mac address of interface\n");
		goto err;
	}
	eth_copy(ping_config.ifaddr,
		 (unsigned char *)ifreq.ifr_hwaddr.sa_data);

	ret = ioctl(sd, SIOCGIFINDEX, &ifreq);
	if (ret < 0) {
		fprintf(stderr, "Error: Can't get interface index\n");
		goto err;
	}
	ping_config.ifindex = ifreq.ifr_ifindex;

	/* add socket to epoll */
	memset(&event, 0, sizeof(event));
	event.events = EPOLLIN;
	event.data.fd = sd;

	if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sd, &event)) {
		fprintf(stderr, "Error: Can't add socket to epoll.\n");
		ret = -EPERM;
		goto err;
	}

	return 0;

err:
	close(sd);
	return ret;
}

static int config_add_host(const char *host) {
	struct ping_host *ping_host;

	ping_host = malloc(sizeof(*ping_host));
	if (!ping_host) {
		fprintf(stderr,
			"Error: Can't allocate host: out-of-memory\n");
		return -ENOMEM;
	}

	if (eth_str2bin(host, ping_host->host) < 0) {
		free(ping_host);
		fprintf(stderr, "Error: invalid MAC address: %s\n", host);
		return -EINVAL;
	}

	if (eth_is_zero(ping_host->host)) {
		free(ping_host);
		fprintf(stderr,
			"Error: zero MAC address not allowed\n");
		return -EINVAL;
	}

	list_add_tail(&ping_host->node, &ping_config.hosts);

	return 0;
}

static void config_free_hosts(void) {
	struct ping_host *host, *tmp;

	list_for_each_entry_safe(host, tmp, &ping_config.hosts, node) {
		list_del(&host->node);
		free(host);
	}
}

static int init_args(int argc, char *argv[])
{
...
	struct option long_opts[] =
	{
		{"help",	no_argument,		0, 'h'},
		{"listen",	no_argument,		0, 'l'},
		{"interface",	required_argument,	0, 'I'},
		{"count",	required_argument,	0, 'c'},
		{"size",	required_argument,	0, 's'},
		{"interval",	required_argument,	0, 'i'},
		{"replyto",	required_argument,	0, 'r'},
		{0, 0, 0, 0}
        };

	while(1) {
		opt = getopt_long(argc, argv, "hlc:s:i:I:r:", long_opts, &opt_idx);
		if (opt == -1)
			break;

		errno = 0;

		switch(opt) {
		case 'h':
			usage(argv[0]);
			exit(2);
			break;
		case 'l':
			ping_config.listen = 1;
			break;
		case 'c':
			ping_config.count = strtoul(optarg, &endptr, 10);
			if (errno != 0 || endptr == optarg ||
			    ping_config.count == 0)
				goto parse_err;
			break;
		case 's':
			ping_config.size = strtoul(optarg, &endptr, 10);
			if (errno != 0 || endptr == optarg)
				goto parse_err;
			break;
		case 'i':
			if (sscanf(optarg, "%f", &interval) == EOF ||
			    interval <= 0 || interval > UINT16_MAX)
				goto parse_err;

			ping_config.interval = (unsigned long)(1000 * interval);
			if (ping_config.interval == 0)
				goto parse_err;
			break;
		case 'I':
			ping_config.ifname = optarg;
			break;
		case 'r':
			if (eth_str2bin(optarg, ping_config.replyto) < 0) {
				fprintf(stderr, "Error: invalid MAC address: %s\n\n",
					argv[optind]);
				exit(2);
			}
			if (eth_is_zero(ping_config.replyto)) {
				fprintf(stderr,
					"Error: zero MAC address not allowed\n\n");
				exit(2);
			}
			break;
		default:
			fprintf(stderr, "\n");
			usage(argv[0]);
			exit(2);
		}
	}

...
}

static int init_request_buffer(void)
{
...

	request_buffer = malloc(len);
	if (!request_buffer)
		return -ENOMEM;

	memset(request_buffer, 0, len);
	eth_copy(request_buffer->ethhdr.h_source, ping_config.ifaddr);
	request_buffer->ethhdr.h_proto = htons(LLCMP_ETHER_TYPE);

	request_buffer->payload_len = htons(ping_config.size);
	request_buffer->type = LLCMP_ECHO_REQUEST;
	request_buffer->identifier = identifier;

	if (eth_is_zero(ping_config.replyto))
		eth_copy(request_buffer->replyto, ping_config.ifaddr);
	else
		eth_copy(request_buffer->replyto, ping_config.replyto);

	return 0;
}

static int init_ping(int argc, char *argv[])
{
	int ret;

	INIT_LIST_HEAD(&ping_config.hosts);

	ret = init_args(argc, argv);
	if (ret < 0)
		return -EINVAL;

	epoll_fd = epoll_create1(0);
	if (epoll_fd < 0) {
		fprintf(stderr, "Can't create epoll file descriptor.\n");
		return -EPERM;
	}

	ret = init_socket();
	if (ret < 0)
		goto err1;

	if (signal(SIGINT, sigint_handler) == SIG_ERR) {
		fprintf(stderr, "Can't establish SIGINT handler.\n");
		ret = -EPERM;
		goto err2;
	}

	if (!ping_config.listen) {
		ret = init_request_buffer();
		if (ret < 0) {
			fprintf(stderr,
				"Can't allocate request buffer: %s\n",
				(ret == -EBUSY) ?
					"no entropy" : "out-of-memory");
			goto err2;
		}
	}

	return 0;

err2:
	close(sd);
err1:
	close(epoll_fd);

	return ret;
}

static int send_packet(struct ping_header *plhdr)
{
...

	ret = sendto(sd, plhdr, sizeof(*plhdr) + ntohs(plhdr->payload_len), 0,
		     (struct sockaddr*)&addr, sizeof(addr));
	if (ret < 0) {
		fprintf(stderr, "Error: Could not send packet: %s\n", strerror(errno));
		return ret;
	}

	return 0;
}

static int send_echo_request(void)
{
...

	list_for_each_entry(host, &ping_config.hosts, node) {
		eth_copy(request_buffer->ethhdr.h_dest, host->host);

		ret = send_packet(request_buffer);
		if (ret < 0)
			return ret;
	}

	rtcidx = (rtcidx + 1) % REQUEST_TIME_CACHE_SIZE;
	clock_gettime(CLOCK_MONOTONIC, &request_time_cache[rtcidx]);

	return 0;
}

static void print_echo_request(struct ping_header *plhdr)
{
...

	eth_bin2str(plhdr->ethhdr.h_source, src);
	eth_bin2str(plhdr->ethhdr.h_dest, dst);
	eth_bin2str(plhdr->replyto, replyto);

	if (plhdr->type == LLCMP_ECHO_REQUEST)
		snprintf(type, sizeof(type), "echo request");
	else if (plhdr->type == LLCMP_ECHO_REPLY)
		snprintf(type, sizeof(type), "echo reply");
	else
		snprintf(type, sizeof(type), "unknown");

	printf("%s > %s, LLCMP, %s, reply-to %s, id 0x%04x, seq %u, length %u(%u)\n",
		src, dst, type, replyto, ntohs(plhdr->identifier),
		ntohs(plhdr->seqno), paylen, pktlen);
}

static int recv_echo_request(struct ping_header *plhdr)
{
	print_echo_request(plhdr);

	plhdr->type = LLCMP_ECHO_REPLY;
	eth_copy(plhdr->ethhdr.h_dest, plhdr->replyto);
	eth_copy(plhdr->ethhdr.h_source, ping_config.ifaddr);
	eth_copy(plhdr->replyto, ping_config.ifaddr);

	return send_packet(plhdr);
}

static int check_seqno_range(const uint16_t recv_seqno)
{
...

	if (send_seqno > REQUEST_TIME_CACHE_SIZE)
		last_seqno = send_seqno - REQUEST_TIME_CACHE_SIZE + 1;

	/* 不支持seqno环绕 */
	if (send_seqno > recv_seqno ||
	    recv_seqno < last_seqno)
		return -ERANGE;

	return 0;
}

static int echo_reply_timediffus_get(unsigned long *timediff,
				     const uint16_t seqno)
{
...

	idx = (seqno - 1) % REQUEST_TIME_CACHE_SIZE;
	clock_gettime(CLOCK_MONOTONIC, &now);
	diff = timespec_diffus(request_time_cache[idx], now);
	diff = diff < 0 ? 0 : diff;

	if (diff > ULONG_MAX)
		return -ERANGE;

	*timediff = (unsigned long)diff;
	return 0;
}

static int recv_echo_reply(struct ping_header *plhdr)
{
...

	eth_bin2str(plhdr->ethhdr.h_source, src);
	eth_bin2str(plhdr->replyto, replyto);

	if (echo_reply_timediffus_get(&timediff_us, ntohs(plhdr->seqno)) < 0)
		printf("%u(%u) bytes from %s (via %s): llcmp_seq=%u\n",
		       paylen, pktlen, replyto, src, ntohs(plhdr->seqno));
	else
		printf("%u(%u) bytes from %s (via %s): llcmp_seq=%u time=%lu.%03lu ms\n",
		       paylen, pktlen, replyto, src, ntohs(plhdr->seqno),
		       timediff_us / 1000, timediff_us % 1000);

	return 0;
}

static int recv_check_header(struct ping_header *plhdr, int len)
{
...

	if (ntohs(plhdr->payload_len) > len - sizeof(*plhdr)) {
		fprintf(stderr,
			"Warning: received malformed packet: payload_len too large (%i > %zu bytes)\n",
			ntohs(plhdr->payload_len), (size_t)len - sizeof(*plhdr));
		return -EINVAL;
	}

	if (ntohs(plhdr->ethhdr.h_proto) != LLCMP_ETHER_TYPE) {
		fprintf(stderr,
			"Warning: received malformed packet: invalid ether type (0x%04x, expected 0x%04x)\n",
			ntohs(plhdr->ethhdr.h_proto), LLCMP_ETHER_TYPE);
		return -EINVAL;
	}

	/* 源代码检查:忽略自己的帧 */
	if (eth_is_own(plhdr->ethhdr.h_source))
		return -EADDRNOTAVAIL;

	/* 目的地检查:只接受多播和自己的地址 */
	if (!eth_is_own(plhdr->ethhdr.h_dest) &&
	    !eth_is_multicast(plhdr->ethhdr.h_dest))
		return -EADDRNOTAVAIL;

	return 0;
}

static int recv_packet(int sd)
{
...

	/* 忽略不适合我们的东西 */
	if (ret == -EADDRNOTAVAIL)
		return 0;
	/* 畸形数据包  */
	else if (ret < 0)
		return ret;

	switch (plhdr->type) {
	case LLCMP_ECHO_REQUEST:
		/* 仅适用于监听 */
		if (!ping_config.listen)
			return 0;

		ret = recv_echo_request(plhdr);
		break;
	case LLCMP_ECHO_REPLY:
		/* 仅适用于请求者 */
		if (ping_config.listen)
			return 0;

		/* 忽略不是来自我们会话的回复 */
		if (plhdr->identifier != request_buffer->identifier)
			return 0;

		ret = recv_echo_reply(plhdr);
		break;
	default:
		fprintf(stderr,
			"Warning: unknown ping type: %u\n", plhdr->type);
		return -EINVAL;
	}

	return ret;
}

static int get_next_timeout()
{
...

	add.tv_sec = ping_config.interval / 1000;
	add.tv_nsec = (ping_config.interval % 1000) * (1000*1000);
	next = timespec_sum(request_time_cache[rtcidx], add);

	diff = timespec_diffus(now, next) / 1000;
	if (diff < 0)
		return 0;

	return (diff > INT_MAX) ? INT_MAX : (int)diff;
}

int main(int argc, char *argv[])
{
...

	while(!term) {
		if (!ping_config.listen) {
			if (!ev_count) {
				if (count &&
				    count <= ntohs(request_buffer->seqno))
					break;

				send_echo_request();
			}

			timeout = get_next_timeout();
		}

		ev_count = epoll_wait(epoll_fd, events, MAX_EVENTS,
				      timeout);

		for(int i = 0; i < ev_count; i++)
			recv_packet(events[i].data.fd);
	}

...

	return 0;
}

...

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

dping利用其自己的非正式以太类型(0x4304),该类型允许在低级别上测试链路,而不依赖于像IPv4/IPv6这样的网络协议。在发送回显请求或回显回复之前,不需要ARP或ICMPv6邻居发现。

要使用dping,请在侦听模式下启动一个dping实例(–listen)。然后,您可以通过在指定了目标主机的MAC地址的不同主机上启动另一个dping实例来ping它。

  • 示例:

    dping可以用于单独测试链路的单播和多播能力。

  • 运行效果:

单播测试:
Listener on ens33/MAC address :

Sender on ens37/MAC address :

广播测试:

回复功能可用于强制侦听器回复广播地址。这样,就可以在不依赖单播的情况下单独测试接口的广播功能。

Listener on ens33/MAC address :

Sender on ens37/MAC address :

在这里插入图片描述
数据包格式

  • 链路层控制消息协议

数据包格式(主要)类似于ICMPv6,即IPv6的互联网控制消息协议。目前,LLCMP回声请求和LLCMP回声回复在dping中实现:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                     Ethernet Destination ...                  |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  ... Ethernet Destination     |       Ethernet Source ...     |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                 ... Ethernet Source                           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|      LLCMP Ethernet Type      |  Reserved1    |  Reserved2    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|      LLCMP Payload Length     |  LLCMP Type   |  Reserved3    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|      LLCMP Identifier         |  LLCMP Sequence Number        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                     LLCMP Reply-To ...                        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  ... LLCMP Reply-To           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

以太网目标:

接收器的6字节MAC地址。除00:00:00:00:00:00外,其他时间都允许。

以太网源:

6字节的发送方MAC地址。只允许单播MAC地址(第一个八位字节的最低有效位设置为0,而不是00:00:00:00:00)。

LLCMP以太网类型:

0x4304(网络字节顺序/big-endian),非官方以太网类型。

预留1:

保留,稍后可能有LLCMP跃点限制

预留2:

为稍后的服务质量保留,可能是LLCMP流量类

LLCMP有效载荷长度:

LLCMP标头之外的字节数。通常为“帧大小-14字节以太网头-16字节LLCMP头”。(待办事项:可能将LLCMP标头大小定义为6字节,因此排除回声请求/回复特定字段“标识符”、“序列号”和“回复”?)

LLCMP类型:

当前实施/定义:

LLCMP回声请求,128

LLCMP回声回复,129

预留3:

保留,以后可能是特定LLCMP类型的代码/子类型或标志

LLCMP标识符:

随机,但对于特定LLCMP回显请求/回复会话固定为2个字节

LLCMP序列号:

一个2字节的序列号(网络字节顺序/big-endian),用于特定的LLCMP回显请求/回复交换。从1开始,每次回显请求增加1。

LLCMP回复:

6字节MAC地址。在LLCMP回声请求中,指定接收器应在其LLCMP回声回复中使用的以太网目的地。在LLCMP中,回声回复等于LLCMP回声回复的以太网源。也可用于检测第2层源NAT/代理。

总结

原始套接字是一种特殊的套接字类型,允许应用程序直接访问和操作网络协议栈中的网络层和传输层协议。使用原始套接字可以实现更底层的网络访问和控制,例如构建和解析自定义的网络数据包。

在实现链路层ping工具时,原始套接字可以用于发送和接收ICMP Echo请求和响应,从而测试网络的连通性、性能和稳定性。

通过原始套接字,我们可以 bypass 操作系统的一些网络协议栈处理,直接操作链路层数据,以及分析 network path 过程中的网络延迟和网络损耗,进而诊断、分析和解决网络问题。使用原始套接字还需要注意权限管理和安全性,并且需要对网络协议和协议栈有一定的了解。

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

参考:RFC 792、RFC 791、RFC 4861、RFC 4443

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1437303.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Android14音频进阶:MediaPlayerService如何启动AudioTrack 下篇(五十六)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒体系统工程师系列【原创干货持续更新中……】🚀 人生格言: 人生从来没有捷径,只…

Swift Combine 发布者订阅者操作者 从入门到精通二

Combine 系列 Swift Combine 从入门到精通一 1. Combine核心概念 你只需要了解几个核心概念&#xff0c;就能使用好 Combine&#xff0c;但理解它们非常重要。 这些概念中的每一个都通过通用协议反映在框架中&#xff0c;以将概念转化为预期的功能。 这些核心概念是&#x…

ubuntu配置conda环境

博主最近新换了电脑&#xff0c;原本的笔记本便被打入了冷宫&#xff0c;后来想到这个电脑也不能浪费&#xff0c;因此想着把原本的电脑重装一下&#xff0c;博主装了个双系统&#xff0c;分别是window10与ubuntu&#xff0c;今天便拿ubuntu系统练下手。 首先安装nvidia驱动 …

【人工智能】神奇的Embedding:文本变向量,大语言模型智慧密码解析(10)

什么是嵌入&#xff1f; OpenAI 的文本嵌入衡量文本字符串的相关性。嵌入通常用于&#xff1a; Search 搜索&#xff08;结果按与查询字符串的相关性排序&#xff09;Clustering 聚类&#xff08;文本字符串按相似性分组&#xff09;Recommendations 推荐&#xff08;推荐具有…

Android 11 访问 Android/data/或者getExternalCacheDir() 非root方式

前言&#xff1a; 需求要求安装三方应用ExternalCacheDir()下载下来的apk文件。 getExternalCacheDir() : /storage/emulated/0/Android/data/com../cache/ 获取访问权限 如果手机安卓版本为Android10的时候,可以在AndroidManifest.xml中添加下列代码 android:requestLegacyExt…

第5章——深度学习入门(鱼书)

第5章 误差反向传播法 上一章中&#xff0c;我们介绍了神经网络的学习&#xff0c;并通过数值微分计算了神经网络的权重参数的梯度&#xff08;严格来说&#xff0c;是损失函数关于权重参数的梯度&#xff09;。数值微分虽然简单&#xff0c;也容易实现&#xff0c;但缺点是计…

12.0 Zookeeper 数据同步流程

在 Zookeeper 中&#xff0c;主要依赖 ZAB 协议来实现分布式数据一致性。 ZAB 协议分为两部分&#xff1a; 消息广播崩溃恢复 消息广播 Zookeeper 使用单一的主进程 Leader 来接收和处理客户端所有事务请求&#xff0c;并采用 ZAB 协议的原子广播协议&#xff0c;将事务请求…

Linux内核与驱动面试经典“小”问题集锦(4)

接前一篇文章&#xff1a;Linux内核与驱动面试经典“小”问题集锦&#xff08;3&#xff09; 问题5 问&#xff1a;Linux内核中内存分配都有哪些方式&#xff1f;它们之间的使用场景都是什么&#xff1f; 备注&#xff1a;这个问题是笔者近期参加蔚来面试时遇到的一个问题。这…

【Web - 框架 - Vue】随笔 - 通过`CDN`的方式使用`VUE 2.0`和`Element UI`

通过CDN的方式使用VUE 2.0和Element UI VUE 网址 https://cdn.bootcdn.net/ajax/libs/vue/2.7.16/vue.js源码 https://download.csdn.net/download/HIGK_365/88815507测试 代码 <!DOCTYPE html> <html lang"en"> <head><meta charset&quo…

Spring Boot整合新版Spring Security:Lambda表达式配置优雅安全

文章目录 1. 引言2. 项目依赖配置3. 使用Lambda表达式配置Spring Security4. 自定义身份验证逻辑5. 认证与授权注解5.1 Secured注解5.2 PreAuthorize和PostAuthorize注解 6. 总结 &#x1f389;Spring Boot整合新版Spring Security&#xff1a;Lambda表达式配置优雅安全 ☆* o(…

一文掌握SpringBoot注解之@Configuration知识文集(6)

&#x1f3c6;作者简介&#xff0c;普修罗双战士&#xff0c;一直追求不断学习和成长&#xff0c;在技术的道路上持续探索和实践。 &#x1f3c6;多年互联网行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责人。 &#x1f389;欢迎 &#x1f44d;点赞✍评论…

2024.2.6 模拟实现 RabbitMQ —— 数据库操作

目录 引言 选择数据库 环境配置 设计数据库表 实现流程 封装数据库操作 针对 DataBaseManager 单元测试 引言 硬盘保存分为两个部分 数据库&#xff1a;交换机&#xff08;Exchange&#xff09;、队列&#xff08;Queue&#xff09;、绑定&#xff08;Binding&#xff0…

使用Volo.Abp读取Sqlite表中数据

书接上文&#xff1a;Abp 从空白的WebApplication中添加EntityFrameworkCore生成数据库 开发环境&#xff1a;.NET6、Volo.Abp 数据库&#xff1a;Sqlite 说明&#xff1a;纯属个人强行入门。我个人觉得按照官网的操作不舒服&#xff0c;所以自己研究着来&#xff0c;请读者…

[NOIP2017 提高组] 宝藏

[NOIP2017 提高组] 宝藏 题目背景 NOIP2017 D2T2 题目描述 参与考古挖掘的小明得到了一份藏宝图&#xff0c;藏宝图上标出了 n n n 个深埋在地下的宝藏屋&#xff0c; 也给出了这 n n n 个宝藏屋之间可供开发的 m m m 条道路和它们的长度。 小明决心亲自前往挖掘所有宝…

Linux 文件比较工具

在Linux系统中&#xff0c;文件比较是一种常见的任务&#xff0c;用于比较两个文件之间的差异。文件比较可以帮助我们找出两个文件的不同之处&#xff0c;或者确定它们是否完全相同。在Linux中&#xff0c;有多种方法可以进行文件比较。 1. diff 在Linux中&#xff0c;diff命…

React+Antd实现省、市区级联下拉多选组件(支持只选省不选市)

1、效果 是你要的效果&#xff0c;咱们继续往下看&#xff0c;搜索面板实现省市区下拉&#xff0c;原本有antd的Cascader组件&#xff0c;但是级联组件必须选到子节点&#xff0c;不能只选省&#xff0c;满足不了页面的需求 2、环境准备 1、react18 2、antd 4 3、功能实现 …

ThreadLocal及阿里(TransmittableThreadLocal,TTL)分析

TTL类关系图 ThreadLocal <- InheritableThreadLocal <- TransmittableThreadLocal 1. ThreadLocal ThreadLocal 类提供线程本地&#xff08;局部&#xff09;变量。每个线程都有自己独立初始化的变量副本。 TheadLocal 允许我们存储仅由特定线程访问的数据&#xff0c;…

JavaWeb后端开发(第一期):Maven基础、Maven的安装配置、如何创建maven项目模块、maven的生命周期

Java后端开发&#xff1a;2024年2月6日 -> LiuJinTao 文章目录 JavaWeb后端开发&#xff08;第一期&#xff09; &#xff1a; maven基础一、 maven介绍1.1 什么maven呢&#xff1a;1.2 maven的作用1.3 maven 模型1.4 maven 仓库 二、maven 安装2.1 配置本地仓库2.2 配置阿里…

c#cad 创建-多线段(三)

运行环境 vs2022 c# cad2016 调试成功 一、程序说明 AutoCAD中创建多段线的。具体解释如下&#xff1a; 获取当前文档和数据库&#xff0c;并创建一个编辑器&#xff08;用于与用户交互&#xff09;。使用事务处理的方式&#xff0c;开始对数据库的操作。打开模型空间&…

LeetCode-第171题-Excel表的序列号

1.题目描述 给你一个字符串 columnTitle &#xff0c;表示 Excel 表格中的列名称。返回 该列名称对应的列序号 。 例如&#xff1a; A -> 1 B -> 2 C -> 3 ... Z -> 26 AA -> 27 AB -> 28 ... 2.样例描述 3.思路描述 遍历时将每个字母与 A 做减法&…