【计网】计网软件编程——Ethernet&ARP

计网
1. 概述
2. 物理层
3. 链路层
4. 网络层
5. 传输层
6. 应用层
7. 一些问题
8. 计网软件编程——Ethernet&ARP


8.1 概述

8.1.1 网络技术发展

网络技术沿着三条主线演变

【计网】计网软件编程——Ethernet&ARP_第1张图片

主线1:Internet技术

广域网,城域网,局域网技术与TCP/IP协议伴随着Internet应用技术发展完善

TCP/IP协议结构特点

TCP/IP参考模型分为四个层次

  • 应用层(application layer)
  • 传输层(transport layer)
  • 网络层(internet layer)
  • 物理层(host to network layer)

特点:

  • 开放的协议标准
  • 独立于特定的计算机硬件与操作系统
  • 独立于特定的网络硬件,适用于局域网,城域网,广域网
  • 统一的网络地址分配方案
  • 提供多种可靠的网络服务
Internet

Internet 是通过路由器实现的多个广域网,城域网,局域网互联的大型网际网

传统的Internet应用:E-mail,Web,Telnet,FTP

新型应用:浏览器,HTML,搜索引擎,Java跨平台编程

主线2:无线网络技术

演变过程

【计网】计网软件编程——Ethernet&ARP_第2张图片
无线局域网归类到局域网领域

Adhoc与无线传感器网络(WSN)作为网络技术研究与发展的另一条主线

无线分组网

无线分组网PRnet(Packet Radio net)

Ad hoc网络

无线自组网Ad hoc

从是否需要基础设施角度,无线网络分为基于基础设施(蜂窝移动通信和移动IP通信)和无基础设施的网络

Ad hoc是一组带有无线通信收发设备的移动节点组成的多跳,临时和无中心的自治系统。网络中的移动节点本身具有路由和转发功能。

无线传感器网络

在Ad hoc网络技术基础上发展出了无线传感器网络WSN和无线网格网WMN

无线传感器网络WSN 通过无线通信方式形成的一个多跳的、自组织的 Ad hoc网格系统,目的是协作地感知、采集和处理网络覆盖区域中感知对象的信息,并发送给观察者

涉及:传感器技术、计算机网络技术、无线传输技术、嵌入式计算机技术、分布式信息处理、微电子、软件编程技术

无线网格网

无线网格网(WMN)的应用使得无线宽带接入Internet

远距离:802.16 WiMAX 技术,50km内提供高传输速率的服务

近距离:802.11 WLAN

WLAN接入点AP可以与邻近的WMN连接。

主线3:网络安全技术

  1. 网络安全体系结构:网络安全威胁分析,网络安全模型与确定网络安全体系,以及对系统安全评估的标准和方法的研究
  2. 网络安全防护技术:防火墙技术,入侵检测与防攻击技术、防病毒技术、安全审计与计算机取证技术,业务持续性技术
  3. 密码学应用技术:对称密码体制和公钥密码体制以及在此基础上的消息认证与数字签名技术,信息隐藏技术,公钥基础设施PKI技术
  4. 网络安全应用:IP安全,VPN安全,电子邮件安全,Web安全与网络信息过滤技术

8.1.2 LAN&MAN&WAN

广域网

指电信运营商负责的通信网络中使用的技术,全球范围,传输技术涉及光纤传输,无线传输,卫星传输

主要研究远距离,宽带,高服务质量的核心交换技术

城域网

承担用户接入的任务

局域网

Ethernet为组建局域网的首选技术

  • G比特以太网保留了传统的Ethernet帧结构,主干网采用光纤作为传输介质,点对点的全双工技术

IP协议直接与Ethernet帧接口

8.2 Ethernet帧结构解析

网络节点间发送数据都要将他放在帧的有效部分,分为一个或多个帧传送

TCP/IP支持多种不同的链路层协议,取决于网络所用的硬件(Ethernet,令牌环网,FDDI)

  • 基于不同的硬件使用不同的帧结构

8.2.1 Ethernet帧结构

【计网】计网软件编程——Ethernet&ARP_第3张图片

  • 帧长度 = 帧头部长度 + 数据长度 ≥ 64 B 帧长度=帧头部长度+数据长度\ge 64B 帧长度=帧头部长度+数据长度64B

帧头部长度18B=目的地址长度6B+源地址6B+类型字段2B+校验字段4B

前导码和帧前定界符

前导码由56位(7B)的 1010...10 比特序列组成,帧前定界符由 10101011 组成,即62位 10 后,11 之后便是帧的目的地址

前导码(同步作用) 接收电路从开始接收比特到进入稳定状态,需要一定的时间,设计前导码的目的是保证接收电路在帧的目的地址到来之前达到正常的接收状态

目的地址与源地址

硬件地址称作MAC地址,地址长度(6B),格式十六进制 00-12-d3-a2-42-a8

为保证MAC地址的唯一性,有专门的组织负责为网卡的生产厂家分配MAC地址

地址分类

单播地址:目的地址第一位为 0 表示单播地址。表示只能被目的地址匹配的主机接收

多播地址:目的地址第一位为 1 表示多播地址。表示能被一组节点接收

广播域:目的地址全 1 。表示可被同一冲突域的主机接收

类型字段

表示网络层所用的协议

0X0800:IP协议

数据字段

数据长度在46~1500B之间,如果数据少于46B需要补齐

帧校验字段

帧检验字段FCS采用32位CRC校验:目的帧MAC,源MAC,类型,数据

8.2.2 Ethernet帧的接收流程

【计网】计网软件编程——Ethernet&ARP_第4张图片

  1. 在一个局域网中,同一时刻只有一台主机处于发送状态,其余都处于接收状态

  2. 对接收到的帧进行长度判断

    若帧长度<64B,则丢弃,进入等待状态

  3. 检查目的帧地址

    单播地址:且为本机地址,则接收该帧

    组播地址:且本主机位于该组,则接收该帧

    广播地址:接收该帧

  4. 对接收到的帧进行CRC校验

    校验正确,则将帧中数据送往上层,报告“成功接收”

    检验错误:

    • 若帧长度是8位的整数倍,则表示传输过程中没有比特丢失或对位错,报告 “帧检验错”
    • 若帧长度不是8位的整数倍,则报告 “帧比特错”

8.2.3 Ethernet帧的发送流程

【计网】计网软件编程——Ethernet&ARP_第5张图片
载波侦听多路访问(CDMA/CD):先听后说,边听边说,冲突停止,延迟重发

随机延迟重发

8.2.4 编程实现对网卡包的监听

配置

【计网】计网软件编程——Ethernet&ARP_第6张图片
【计网】计网软件编程——Ethernet&ARP_第7张图片
【计网】计网软件编程——Ethernet&ARP_第8张图片
【计网】计网软件编程——Ethernet&ARP_第9张图片

抓包解析

  1. 获取网卡
  2. 选择指定网卡进行抓包
  3. 将获得的数据写入文件
# include
# include //开发win32套接字头文件
# include //抓包

FILE *fp;

void packet_handler(u_char *param,
	const struct pcap_pkthdr *header,
	const u_char *pkt_data);
void pprint_mac(FILE *f, const u_char *p);

int main() {
	printf("Hello World!\n");
	SetDllDirectory((LPCWSTR)"C:\\Windows\\System32\\Npcap"); //system32下级目录

	//在Linux中将设备看做文件,文件使用指针进行操作
	pcap_if_t *alldevs;//设备链表
	pcap_if_t *d;//指向某一设备的指针
	int i = 0, inum;
	char errbuf[PCAP_ERRBUF_SIZE];//保存返回的错误信息

	/*1. 从本机获取设备列表,若未找到,则返回错误并退出*/
	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);
	}

	/*2. 打印设备列表*/
	for (d = alldevs; d != NULL; d = d->next){
		printf("%d. %s\n", ++i, d->description);

		/*打印ip*/
		pcap_addr *a;
		for (a = d->addresses; a; a = a->next) {
			if (AF_INET == a->addr->sa_family) {//IPv4
												//((sockaddr_in *)a->addr)->sin_addr; //通用套接字指针,需要进行转化
												//sin_port端口号 输出in_32位整数
												//inet_ntoa将整数的ipv4地址转换成字符串 有风险
				printf(" %s\n", inet_ntoa(((sockaddr_in *)a->addr)->sin_addr));
			}
		}

		/*
		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 0;
	}

	printf("\n");

	/*3. 输入序号,查看是否有目标网卡*/
	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;
	}

	/*4. 将设备指针切换到目标网卡*/
	for (d = alldevs, i = 0; i< inum - 1; d = d->next, i++);

	/*获取设备句柄*/
	pcap_t *adhandle;

	if ((adhandle = pcap_open(d->name, // name of the device
		65536, // portion of the packet to capture
			   // 65536 guarantees that the whole packet will
			   // be captured on all the link layers
		PCAP_OPENFLAG_PROMISCUOUS, // promiscuous mode
		1000, // read timeout
		NULL, // authentication on the remote machine
		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);
	printf("%s\n", d->name);

	/*5. 打开文件,同步数据*/
	fp = fopen("D:\\capture22.txt", "w");
	/*捕包,解析*/
	/* start the capture */
	//需定义包处理函数
	pcap_loop(adhandle, 0, packet_handler, NULL);


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

	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){
	//printf("%.6d len:%d\n",header->ts.tv_usec, header->len);
	//	header->caplen //实时捕获 ts:时间戳 len:打开文件才能查看
	pprint_mac(fp, pkt_data);//目的mac

	fprintf(fp, "<=");

	pprint_mac(fp, pkt_data+6);//源mac

	fprintf(fp, " 0X%02X%02X", pkt_data[12], pkt_data[13]);//协议类型
	//0X0800表示IP协议
														   //输出数据长度
	fprintf(fp, " %d\n", header->caplen);//报文长度,总长,包含以太网头部

	fflush(fp);
}

void pprint_mac(FILE *f, const u_char *p) {
	fprintf(f, "%02X-%02X-%02X-%02X-%02X-%02X", p[0], p[1], p[2], p[3], p[4], p[5], p[6]);
}

【计网】计网软件编程——Ethernet&ARP_第10张图片

8.3 ARP协议

同一局域网内的不同主机之间根据MAC直接进行通信

传输层/网络层传来的数据包只包含目的主机的IP地址

ARP(address resolution protocal):根据目的主机的IP地址获得其MAC地址,并保持映射关系

8.3.1 ARP扫描

获取网络中的ARP报文

在拦截Ethernet报文的基础上添加 ARP 的过滤器

/*6. 添加过滤器,只获取ARP报文*/
	//compile the filter 针对ARP协议过滤
	bpf_program fcode;
	bpf_u_int32 mask = ((sockaddr_in *)d->addresses)->sin_addr.S_un.S_addr;
	//配置过滤器
	char packet_filter[] = "ether proto \\arp";//要抓取的包的类型,这里是抓取ARP包
	if (pcap_compile(adhandle, &fcode, packet_filter, 1, mask) < 0){
		fprintf(stderr,
			"\nUnable to compile the packet filter. Check the syntax.\n");
		/* Free the device list */
		pcap_freealldevs(alldevs);
		return -1;
	}
	//为设备添加过滤器
	if (pcap_setfilter(adhandle, &fcode) < 0){
		fprintf(stderr, "\nError setting the filter.\n");
		/* Free the device list */
		pcap_freealldevs(alldevs);
		return -1;
	}

【计网】计网软件编程——Ethernet&ARP_第11张图片

发送ARP请求报文

  1. 定义ARP请求报文结构

    #pragma pack (1) //使结构体按1字节方式对齐
    struct ethernet_head
    {
    	unsigned char dest_mac[6];		//目标主机MAC地址
    	unsigned char source_mac[6];	//源端MAC地址
    	unsigned short eh_type;			//以太网类型
    };
    
    struct arp_head
    {
    	unsigned short hardware_type;	//硬件类型:以太网接口类型为1
    	unsigned short protocol_type;	//协议类型:IP协议类型为0X0800
    	unsigned char add_len;			//硬件地址长度:MAC地址长度为6B
    	unsigned char pro_len;			//协议地址长度:IP地址长度为4B
    	unsigned short option;			//操作:ARP请求为1,ARP应答为2
    	unsigned char sour_addr[6];		//源MAC地址:发送方的MAC地址
    	unsigned long sour_ip;			//源IP地址:发送方的IP地址
    	unsigned char dest_addr[6];		//目的MAC地址:ARP请求中该字段没有意义;ARP响应中为接收方的MAC地址
    	unsigned long dest_ip;			//目的IP地址:ARP请求中为请求解析的IP地址;ARP响应中为接收方的IP地址
    	unsigned char padding[18];
    };
    
    struct arp_packet				//最终arp包结构
    {
    	ethernet_head eth;			//以太网头部
    	arp_head arp;				//arp数据包头部
    };
    
    #pragma pack() //恢复对齐方式
    
  2. 创建包构造函数

    void BuildARPReqRequestPacket(const unsigned char *srcMAC, const unsigned long sour_ip,
    	const unsigned long dest_ip, struct arp_packet *pkt) {
    	memset(pkt->eth.dest_mac, 0xFF, 6);
    	memcpy(pkt->eth.source_mac, srcMAC, 6);
    	pkt->eth.eh_type = htons(0x0806);
    
    	pkt->arp.hardware_type = htons(1);
    	pkt->arp.protocol_type = htons(0x0800);
    	pkt->arp.add_len = 6;
    	pkt->arp.pro_len= 4;
    	pkt->arp.option = htons(1);
    	memcpy(pkt->arp.sour_addr, srcMAC, 6);
    	pkt->arp.sour_ip = sour_ip;// inet_addr("192.168.1.3");
    	memset(pkt->arp.dest_addr, 0, 6);//目的MAC
    	pkt->arp.dest_ip = dest_ip;//inet_addr("192.168.1.6");
    	memset(pkt->arp.padding, 0, 18);
    }
    
  3. 轮询发送线程

    DWORD WINAPI ARPSendThread(LPVOID p) {
    	MyParam * param = (MyParam *)p;
    
    	//本机IP,网络字节顺序
    	unsigned long localIP = param->ip;
    	//子网掩码,网络字节顺序
    	unsigned long netmask = param->mask;
    	//网络总主机数目,子网掩码按位取反即可
    	unsigned int netsize = ntohl(~netmask);
    
    	//子网地址,网络字节顺序
    	unsigned long net = localIP&netmask;
    	struct arp_packet pkt;
    	unsigned long ip = 0;
    
    	for (unsigned int i = 1; i<netsize; i++) {
    		//网络中第i台主机的子网内地址,网络字节顺序
    		unsigned long n = htonl(i);
    		//第i台主机的IP地址,网络字节顺序
    		unsigned long ip = net | n;
    
    		//此处出错 目的ip构造
    		BuildARPReqRequestPacket(param->mac, localIP, ip, &pkt);
    		pcap_sendpacket(param->handle, (const u_char *)&pkt, sizeof(pkt));
    	}
    
    	return 0;
    }
    
  4. 启动线程

    DWORD tid;
    HANDLE h = CreateThread(0, 0, ARPSendThread, &myparam, 0, &tid);
    

获取响应报文

unsigned short type = *(unsigned short*)&pkt_data[12];
if (htons(0x0806) != type) {
    return;
}

arp_packet *p = (arp_packet *)&pkt_data[14];
unsigned short opcode = *(unsigned short*)&pkt_data[20];
if (opcode == htons(0X0002)) { //ARP response
    printf("arp response\n");
    print_mac(stdout, pkt_data);
    printf("<=");
    print_mac(stdout, pkt_data + 6);
    struct in_addr inaddr;
    inaddr.S_un.S_addr = *(unsigned long long *)&pkt_data[28];
    printf(":%s\n", inet_ntoa(inaddr));
}

解释一下为什么是 pkt_data[20]

【计网】计网软件编程——Ethernet&ARP_第12张图片
如图,以太网帧占14B(除去前导码和帧界定符),ARP请求报文实际从第15B开始,也就是 pkt_data[14]

而ARP首部占6B,所以从第21B开始,即 pkt_data[20]

*(unsigned long long *)&pkt_data[28]

pkt_data[28] 开始,取 32 bit 的二进制序列

所有代码

mylib.h

#pragma once
//可被多次包含,但只被定义一次
#ifndef _MYLIB_H_
#define _MYLIB_H_

# include
# include

#pragma pack (1) //使结构体按1字节方式对齐
struct ethernet_head
{
	unsigned char dest_mac[6];		//目标主机MAC地址
	unsigned char source_mac[6];	//源端MAC地址
	unsigned short eh_type;			//以太网类型
};

struct arp_head
{
	unsigned short hardware_type;	//硬件类型:以太网接口类型为1
	unsigned short protocol_type;	//协议类型:IP协议类型为0X0800
	unsigned char add_len;			//硬件地址长度:MAC地址长度为6B
	unsigned char pro_len;			//协议地址长度:IP地址长度为4B
	unsigned short option;			//操作:ARP请求为1,ARP应答为2
	unsigned char sour_addr[6];		//源MAC地址:发送方的MAC地址
	unsigned long sour_ip;			//源IP地址:发送方的IP地址
	unsigned char dest_addr[6];		//目的MAC地址:ARP请求中该字段没有意义;ARP响应中为接收方的MAC地址
	unsigned long dest_ip;			//目的IP地址:ARP请求中为请求解析的IP地址;ARP响应中为接收方的IP地址
	unsigned char padding[18];
};

struct arp_packet				//最终arp包结构
{
	ethernet_head eth;			//以太网头部
	arp_head arp;				//arp数据包头部
};

#pragma pack() //恢复对齐方式

// ifname :接口名字
// mac:输出获取到的mac地址
bool get_mac(const char *ifname, unsigned char *mac);
//向文件指针,从偏移字节开始输出字节
void print_mac(FILE *fp, const unsigned char *p);

void BuildARPReqRequestPacket(const unsigned char *srcMAC, const unsigned long sour_ip,
	const unsigned long dest_ip, struct arp_packet *pkt);

#endif

mylib.cpp

# include "mylib.h"

#include 
//arp头文件
#include 
#include 

//不生效# define _WINSOCK_DEPRECATED_NO_WARNINGS 1

void print_mac(FILE *f, const u_char *p) {
	fprintf(f, "%02X-%02X-%02X-%02X-%02X-%02X", p[0], p[1], p[2], p[3], p[4], p[5], p[6]);
}

bool get_mac(const char *ifname, unsigned char *mac) {
	LPADAPTER lpAdapter = PacketOpenAdapter(ifname);
	if (!lpAdapter || (lpAdapter->hFile == INVALID_HANDLE_VALUE)) {
		return false;//打开失败,返回错误
	}

	PPACKET_OID_DATA OidData = (PPACKET_OID_DATA)malloc(6 + sizeof(PACKET_OID_DATA));
	if (OidData == NULL) {
		PacketCloseAdapter(lpAdapter);
		return false;
	}

	OidData->Oid = OID_802_3_CURRENT_ADDRESS;
	OidData->Length = 6;
	ZeroMemory(OidData->Data, 6);

	BOOLEAN Status = PacketRequest(lpAdapter, FALSE, OidData);
	if (Status) {
		memcpy(mac, OidData->Data, 6);

		printf("The MAC address of the adapter is %.2x-%.2x-%.2x-%.2x-%.2x-%.2x\n",
			(PCHAR)(OidData->Data)[0],
			(PCHAR)(OidData->Data)[1],
			(PCHAR)(OidData->Data)[2],
			(PCHAR)(OidData->Data)[3],
			(PCHAR)(OidData->Data)[4],
			(PCHAR)(OidData->Data)[5]);
		free(OidData);
		PacketCloseAdapter(lpAdapter);

		return true;
	}
	else {
		printf("error retrieving the MAC address of the adapter!\n");
		free(OidData);
		PacketCloseAdapter(lpAdapter);
		return false;
	}
}

void BuildARPReqRequestPacket(const unsigned char *srcMAC, const unsigned long sour_ip,
	const unsigned long dest_ip, struct arp_packet *pkt) {
	memset(pkt->eth.dest_mac, 0xFF, 6);
	memcpy(pkt->eth.source_mac, srcMAC, 6);
	pkt->eth.eh_type = htons(0x0806);

	pkt->arp.hardware_type = htons(1);
	pkt->arp.protocol_type = htons(0x0800);
	pkt->arp.add_len = 6;
	pkt->arp.pro_len= 4;
	pkt->arp.option = htons(1);
	memcpy(pkt->arp.sour_addr, srcMAC, 6);
	pkt->arp.sour_ip = sour_ip;// inet_addr("192.168.1.3");
	memset(pkt->arp.dest_addr, 0, 6);//目的MAC
	pkt->arp.dest_ip = dest_ip;//inet_addr("192.168.1.6");
	memset(pkt->arp.padding, 0, 18);
}

ARPScan.cpp

# include "mylib.h"

#include 
//arp头文件
#include 
#include 

//不生效# define _WINSOCK_DEPRECATED_NO_WARNINGS 1

void print_mac(FILE *f, const u_char *p) {
	fprintf(f, "%02X-%02X-%02X-%02X-%02X-%02X", p[0], p[1], p[2], p[3], p[4], p[5], p[6]);
}

bool get_mac(const char *ifname, unsigned char *mac) {
	LPADAPTER lpAdapter = PacketOpenAdapter(ifname);
	if (!lpAdapter || (lpAdapter->hFile == INVALID_HANDLE_VALUE)) {
		return false;//打开失败,返回错误
	}

	PPACKET_OID_DATA OidData = (PPACKET_OID_DATA)malloc(6 + sizeof(PACKET_OID_DATA));
	if (OidData == NULL) {
		PacketCloseAdapter(lpAdapter);
		return false;
	}

	OidData->Oid = OID_802_3_CURRENT_ADDRESS;
	OidData->Length = 6;
	ZeroMemory(OidData->Data, 6);

	BOOLEAN Status = PacketRequest(lpAdapter, FALSE, OidData);
	if (Status) {
		memcpy(mac, OidData->Data, 6);

		printf("The MAC address of the adapter is %.2x-%.2x-%.2x-%.2x-%.2x-%.2x\n",
			(PCHAR)(OidData->Data)[0],
			(PCHAR)(OidData->Data)[1],
			(PCHAR)(OidData->Data)[2],
			(PCHAR)(OidData->Data)[3],
			(PCHAR)(OidData->Data)[4],
			(PCHAR)(OidData->Data)[5]);
		free(OidData);
		PacketCloseAdapter(lpAdapter);

		return true;
	}
	else {
		printf("error retrieving the MAC address of the adapter!\n");
		free(OidData);
		PacketCloseAdapter(lpAdapter);
		return false;
	}
}

void BuildARPReqRequestPacket(const unsigned char *srcMAC, const unsigned long sour_ip,
	const unsigned long dest_ip, struct arp_packet *pkt) {
	memset(pkt->eth.dest_mac, 0xFF, 6);
	memcpy(pkt->eth.source_mac, srcMAC, 6);
	pkt->eth.eh_type = htons(0x0806);

	pkt->arp.hardware_type = htons(1);
	pkt->arp.protocol_type = htons(0x0800);
	pkt->arp.add_len = 6;
	pkt->arp.pro_len= 4;
	pkt->arp.option = htons(1);
	memcpy(pkt->arp.sour_addr, srcMAC, 6);
	pkt->arp.sour_ip = sour_ip;// inet_addr("192.168.1.3");
	memset(pkt->arp.dest_addr, 0, 6);//目的MAC
	pkt->arp.dest_ip = dest_ip;//inet_addr("192.168.1.6");
	memset(pkt->arp.padding, 0, 18);
}

【计网】计网软件编程——Ethernet&ARP_第13张图片

你可能感兴趣的:(#,计算机网络,计算机基础,网络)