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

在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.mfbz.cn/a/375386.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++ //练习 4.23 因为运算符的优先级问题,下面这条表达式无法通过编译。根据4.12节中的表(第147页)指出它的问题在哪里?应该如何修改?

C Primer&#xff08;第5版&#xff09; 练习 4.23 练习 4.23 因为运算符的优先级问题&#xff0c;下面这条表达式无法通过编译。根据4.12节中的表&#xff08;第147页&#xff09;指出它的问题在哪里&#xff1f;应该如何修改&#xff1f; string s "word"; stri…

树莓派4b连接WQ9201外置无线网卡命令行配置详解

树莓派4B连接WQ9201无线网卡 接线方式 蓝色的线来连接树莓派和WQ9201demo板&#xff0c;USB接树莓派的USB接口&#xff0c;microUSB一端接demo板靠近天线部分的microUSB口。 驱动和固件准备 驱动直接放在树莓派系统的任意目录&#xff0c;目前配置则是将驱动放在树莓派的主目…

【漏洞复现】电信网关配置管理系统SQL注入漏洞

Nx01 产品简介 电信网关配置管理系统是一个用于管理和配置电信网络中网关设备的软件系统。它可以帮助网络管理员实现对网关设备的远程监控、配置、升级和故障排除等功能&#xff0c;从而确保网络的正常运行和高效性能。 Nx02 漏洞描述 电信网关配置管理系统存在SQL注入漏洞,攻…

【华为云】云上两地三中心实践实操

写在前面 应用上云之后&#xff0c;如何进行数据可靠性以及业务连续性的保障是非常关键的&#xff0c;通过华为云云上两地三中心方案了解相关方案认证地址&#xff1a;https://connect.huaweicloud.com/courses/learn/course-v1:HuaweiXCBUCNXI057Self-paced/about当前内容为华…

《动手学深度学习(PyTorch版)》笔记7.4

注&#xff1a;书中对代码的讲解并不详细&#xff0c;本文对很多细节做了详细注释。另外&#xff0c;书上的源代码是在Jupyter Notebook上运行的&#xff0c;较为分散&#xff0c;本文将代码集中起来&#xff0c;并加以完善&#xff0c;全部用vscode在python 3.9.18下测试通过&…

苹果macbook电脑删除数据恢复该怎么做?Mac电脑误删文件的恢复方法

苹果电脑删除数据恢复该怎么做&#xff1f;Mac电脑误删文件的恢复方法 如何在Mac上恢复误删除的文件&#xff1f;在日常使用Mac电脑时&#xff0c;无论是工作还是娱乐&#xff0c;我们都会创建和处理大量的文件。然而&#xff0c;有时候可能会不小心删除一些重要的文件&#x…

Stata学习(1)

一、五大窗口 Command窗口&#xff1a;实现人机交互 来导入一个自带数据&#xff1a; sysuse是导入系统自带的数据&#xff0c;auto导入该数据的名称&#xff0c;后面的clear是清除之前的数据 结果窗口&#xff1a;展示计算结果、查找功能 在Edit的find可以实现查找功能&#…

企业飞书应用机器人,使用python发送图文信息到群

企业飞书应用的自动化&#xff0c;需要创建企业应用&#xff0c;应用开通机器人能力&#xff0c;并获取机器人所需的app_id与app_secret&#xff08;这一部分大家可以在飞书的控制台获取&#xff1a;https://open.feishu.cn/api-explorer/&#xff09; 文章目录 步骤1&#xff…

第 383 场 LeetCode 周赛题解

A 边界上的蚂蚁 模拟 class Solution { public:int returnToBoundaryCount(vector<int> &nums) {int s 0;int res 0;for (auto x: nums) {s x;if (s 0)res;}return res;} };B 将单词恢复初始状态所需的最短时间 I 枚举&#xff1a;若经过 i i i 秒后 w o r d w…

c语言--指针运算

目录 一、指针-整数二、指针-指针2.1条件2.2两个指针指向同一块空间代码2.2.1运行结果 2.3两个指针指向不同块空间代码2.3.1运行结果 2.4总结 三、指针的关系运算3.1代码3.1.1运行结果3.1.2分析 一、指针整数 用数组举例&#xff1a; 因为数组在内存中是连续存放的&#xff0c…

证券公司vip快速交易通道是什么?是免费的吗?

券商VIP快速交易通道&#xff0c;又称为“快速通道”&#xff0c;是券商向交易所申请后提供给客户的一种特别服务。这是一种独立的交易单元&#xff0c;具有更快的传输速度和更高的优先级。在快速交易通道中&#xff0c;客户的订单数据会通过极速交易柜台发起&#xff0c;走的是…

vscode的ssh忽然连不上服务器:远程主机可能不符合glibc和libstdc++ VS Code服务器的先决条件

vscode自动更新了一下就发现连不上服务器了&#xff0c;我寻思估计一大堆人都寄了&#xff0c;一搜&#xff0c;果然哈哈哈哈 然后我直接搜一天内新发布的博客&#xff0c;还真给我搜到了这个问题&#xff0c;按照这个问题里面的回答&#xff08;vscode1.86无法远程连接waitin…

ES6扩展运算符——三个点(...)用法详解

目录 1 含义 2 替代数组的 apply 方法 3 扩展运算符的应用 &#xff08; 1 &#xff09;合并数组 &#xff08; 2 &#xff09;与解构赋值结合 &#xff08; 3 &#xff09;函数的返回值 &#xff08; 4 &#xff09;字符串 &#xff08; 5 &#xff09;实现了 Iter…

Git基础命令,分支,标签的使用【快速入门Git】

Git基础命令&#xff0c;分支&#xff0c;标签的使用【快速入门Git】 Git基础常用命令Git工作流程工作区&#xff0c;暂存区和版本库文件状态获取Git仓库 git init | git clone查看文件状态 git status暂存已修改的文件 git add 查看已暂存和未暂存的修改 git diff提交文件更改…

数据结构——D/二叉树

&#x1f308;个人主页&#xff1a;慢了半拍 &#x1f525; 创作专栏&#xff1a;《史上最强算法分析》 | 《无味生》 |《史上最强C语言讲解》 | 《史上最强C练习解析》 &#x1f3c6;我的格言&#xff1a;一切只是时间问题。 ​ 1.树概念及结构 1.1树的概念 树是一种非线性的…

excel 导出 The maximum length of cell contents (text) is 32767 characters

导出excel报错。错误日志提示&#xff1a;:The maximum length of cell contents (text) is 32767 characters 排查后&#xff0c;发现poi有单元格最大长度校验&#xff0c;超过32767会报错。 解决方案&#xff1a; 通过java反射机制&#xff0c;设置单元格最大校验限制为Int…

【Git教程】(一)基本概念:—— 工作流、分布式版本控制、版本库 ~

Git教程 基本概念 1️⃣ 为什么要用 Git2️⃣ 为什么要用工作流3️⃣ 分布式版本控制4️⃣ 版本库5️⃣ 简单的分支创建与合并&#x1f33e; 总结 在本章中&#xff0c;将介绍一个分布式版本控制系统的设计思路&#xff0c;以及它与集中式版本控制系统的不同之处。除此之外&am…

鸿蒙开发-UI-组件导航-Tabs

鸿蒙开发-UI-组件 鸿蒙开发-UI-组件2 鸿蒙开发-UI-组件3 鸿蒙开发-UI-气泡/菜单 鸿蒙开发-UI-页面路由 鸿蒙开发-UI-组件导航-Navigation 文章目录 一、基本概念 二、导航 1.底部导航 2.顶部导航 3.侧边导航 4.导航栏限制滑动 三、导航栏 1.固定导航栏 2.滚动导航栏 3…

Verilog刷题笔记21

题目&#xff1a; A priority encoder is a combinational circuit that, when given an input bit vector, outputs the position of the first 1 bit in the vector. For example, a 8-bit priority encoder given the input 8’b10010000 would output 3’d4, because bit[4…

93.网游逆向分析与插件开发-游戏窗口化助手-升级经验数据获取的逆向分析

内容参考于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;显示游戏数据到小助手UI 码云地址&#xff08;游戏窗口化助手 分支&#xff09;&#xff1a;https://gitee.com/dye_your_fingers/sro_-ex.git 码云版本号&#xff1a;852c339f5e4c103390b123e0eaed…