ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

以太网点对点协议之pppoe协议讲解

2021-01-02 23:01:05  阅读:390  来源: 互联网

标签:协议 tagstart 以太网 TAG printf PPPOE pppoe PPPoE


PPPoE是以太网点对点协议的首字母缩写(Point to Point Protocol over Ethernet)。PPPoE是从另一个称为PPP的旧协议派生的网络协议,即点对点协议。PPPoE的创建是为了管理如何通过以太网(有线网络)传输数据。这使我们可以使用以太网在多个客户端之间共享单个服务器连接。

IETF在1999年发布了PPPoE协议的工作标准。PPPoE的IETF规范是RFC2516。

在这里插入图片描述

下面开始介绍pppoe协议。

以太网点对点协议(PPPoE)

PPPoE的工作流程包含发现( Discovery) 和会话( Session) 两个阶段。

发现阶段
在这里插入图片描述

发现阶段有四个步骤。 完成后,两者对等方知道PPPoE SESSION_ID和对等方的以太网地址,一起共同定义PPPoE会话。 步骤包括广播发起数据包,一个或多个访问的主机的数量集中器发送报价包,主机发送单播会话请求数据包和选定的访问集中器发送确认包。 主机收到确认数据包后,它可以进入PPP会话阶段。 当访问集中器发送确认数据包,它可以进行到PPP会议阶段。所有发现以太网帧的ETHER_TYPE字段均设置为值0x8863。

在发现阶段可能有五个不同的值:

0x09:PPPoE主动发现启动(PADI)数据包
0x07:PPPoE主动发现提议(PADO)数据包
0x19:PPPoE主动发现请求(PADR)数据包
0x65:PPPoE Active Discovery会话确认(PADS)数据包
0xa7:PPPoE Active Discovery Terminate(PADT)数据包

PADI是由客户端发送的广播广播的初始化消息,以发现是否有任何服务器。PADO是单播提供给请求的客户端的服务器的答案。PADR是Client的选择消息。此消息发送到所选服务器。PADS是服务器发送的设置消息。在此最后阶段发送会话ID,并建立会话。

PPPoE主动发现启动(PADI)数据包
在这里插入图片描述
主机将DESTINATION_ADDR设置为PADI的数据包发送到广播地址。 CODE字段设置为0x09,SESSION_ID 必须设置为0x0000。PADI封包务必包含TAG_TYPE服务中的一个TAG,名称,指示主机正在请求的服务,以及任何数字其他TAG类型。 整个PADI数据包(包括PPPoE标头)不得超过1484个八位位组,以留出足够的空间让中继代理添加中继会话ID标记。

PPPoE主动发现提议(PADO)数据包

在这里插入图片描述

当访问集中器收到其可以服务的PADI时,它将通过发送PADO数据包进行回复。DESTINATION_ADDR是 发送PADI的主机的单播地址。 CODE字段是设置为0x07,并且SESSION_ID必须设置为0x0000。PADO数据包必须包含一个包含访问的AC名称标签集中器的名称,即服务名称TAG,与PADI,以及任何其他表示其他的服务名称TAG访问集中器提供的服务。 如果访问集中器不能为PADI服务,它一定不能以PADO响应。

PPPoE主动发现请求(PADR)数据包:

在这里插入图片描述

由于PADI已广播,主机可能会收到不止一个 PADO,浏览接收到的PADO数据包,选择一个。 该选择可以基于AC名称或服务提供。 然后,主机向访问服务器发送一个PADR数据包选择的集中器。 设置了DESTINATION_ADDR字段到发送的访问集中器的单播以太网地址PADO。 CODE字段设置为0x19,并且SESSION_ID必须为设置为0x0000。PADR数据包务必包含TAG_TYPE服务中的一个TAG名称,指示主机正在请求的服务,以及任何数字其他TAG类型。

PPPoE Active Discovery会话确认(PADS)数据包:

在这里插入图片描述

当访问集中器接收到PADR数据包时,它准备进行以下操作:开始PPP会话。 它为PPPoE生成唯一的SESSION_ID会话并使用PADS数据包回复主机。 的DESTINATION_ADDR字段是主机的单播以太网地址发送了PADR。 CODE字段设置为0x65,SESSION_ID必须设置为为此PPPoE会话生成的唯一值。PADS数据包仅包含一个TAG_TYPE服务名称的TAG,表明访问集中器已接受的服务PPPoE会话,以及任何其他数量的TAG类型。如果访问集中器不喜欢PADR,那么它必须使用包含TAG_TYPE TAG的PADS进行回复服务名称错误(以及任何其他数量的TAG类型)。 在这种情况下SESSION_ID必须设置为0x0000。

PPPoE Active Discovery Terminate(PADT)数据包:

在这里插入图片描述

建立会话后,可以随时发送此数据包。表示PPPoE会话已终止。 它可能是由主机或访问集中器。 DESTINATION_ADDR字段是单播以太网地址,CODE字段设置为0xa7并且必须设置SESSION_ID来指示要进行哪个会话终止。 不需要TAG。 收到PADT时,不允许再发送PPP流量使用该会话。 即使是普通的PPP终止数据包也不得发送或接收PADT后发送。 PPP对等体应使用PPP协议本身可以降低PPPoE会话,但是PADT可以无法使用PPP时使用。

会话阶段

一旦PPPoE会话开始,便像其他任何PPP一样发送PPP数据封装。 所有以太网数据包都是单播的。 ETHER_TYPE字段设置为0x8864。 PPPoE代码必须设置为0x00。 SESSION_ID不得更改该PPPoE会话,且必须为发现阶段分配的值。 PPPoE有效负载包含一个PPP框架。 帧以PPP协议ID开头。
在这里插入图片描述
访问集中器可以在向客户端发送PADS数据包之后启动PPPoE会话,或者客户端可以在从访问集中器接收到PADS数据包之后开始PPPoE会话。一个设备在每个接口上支持多个PPPoE会话,但每个设备最多不超过256个PPPoE会话。

每个PPPoE会话由对等方的以太网地址和会话ID唯一标识。建立PPPoE会话后,将像在其他任何PPP封装中一样发送数据。PPPoE信息封装在以太网帧中,并发送到单播地址。

回显请求和所有其他PPP流量的行为与正常PPP会话中的行为完全相同。在此阶段,客户端和服务器都必须为PPPoE逻辑接口分配资源。

建立会话后,客户端或访问集中器可以随时发送PPPoE主动发现终止(PADT)数据包以终止会话。PADT数据包包含对等方的目标地址和要终止的会话的会话ID。发送此数据包后,会话将关闭PPPoE通信。

PPP使用链接控制协议(LCP)在用户计算机和ISP之间建立会话。LCP负责确定链路是否可接受数据传输。LCP数据包在多个网络点之间交换,以确定链路特征,包括设备标识,数据包大小和配置错误。

判断是不是PPPoE协议

static bool is_pppope(struct ether_header *pEther)
{
	
	printf("info pppoe\n");
	struct pppoe_hdr *pppoe_h;
	pppoe_h = (pppoe_hdr *)(pEther + 1);
    //PPPoED
    if( ntohs(pEther->ether_type) == 0x8863 && pppoe_h->code == PADT_CODE)
    {
        return true;
    }
    //PPPoES
    if( ntohs(pEther->ether_type) == 0x8864 )
    {
		return true;
    }
    return false;
	
}

PPPoE的以太网有效负载

以太网:

以太网帧的类型字段确定哪个阶段处于活动状态。在这里,您可以找到0x8863进行发现(Discovery)或0x8864进行会话(Session)。类型字段后跟PPPoE帧,该帧嵌入在以太网帧的数据字段中。

在这里插入图片描述
DESTINATION_ADDR字段包含单播以太网目标地址或以太网广播地址。对于发现数据包,该值为单播或广播地址在“发现”部分中定义。 对于PPP会话流量,此字段必须包含对等方的单播地址,如下所示:从发现阶段确定。SOURCE_ADDR字段必须包含源设备。ETHER_TYPE设置为0x8863(发现阶段)或0x8864(PPP会话阶段)。

在这里插入图片描述

在这里插入图片描述

VER字段为4位,对于此版本的VER字段,必须将其设置为0x1 。TYPE字段为4位,此版本必须设置为0x1,CODE字段是八位。下面为发现定义和PPP会话阶段。

SESSION_ID字段为16位。 这是一个无符号值网络字节顺序。 它的值在下面为发现定义
包。 该值对于给定的PPP会话是固定的,实际上,与以太网SOURCE_ADDR一起定义PPP会话,并且DESTINATION_ADDR。 LENGTH字段是16位。 该值(以网络字节顺序)指示PPPoE有效负载的长度。 它不包括以太网或PPPoE标头的长度。

剖析发现PPPoE协议标签

下面是协议标签的宏定义:

#define PPPOE_TAG_EOL         0x0000
#define PPPOE_TAG_SVC_NAME    0x0101 /*Service-Name*/
#define PPPOE_TAG_AC_NAME     0x0102 /*AC-Name*/
#define PPPOE_TAG_HOST_UNIQ   0x0103
#define PPPOE_TAG_AC_COOKIE   0x0104
#define PPPOE_TAG_VENDOR      0x0105
#define PPPOE_TAG_CREDITS     0x0106
#define PPPOE_TAG_METRICS     0x0107
#define PPPOE_TAG_SEQ_NUM     0x0108
#define PPPOE_TAG_CRED_SCALE  0x0109
#define PPPOE_TAG_RELAY_ID    0x0110
#define PPPOE_TAG_HURL        0x0111
#define PPPOE_TAG_MOTM        0x0112
#define PPPOE_TAG_MAX_PAYLD   0x0120
#define PPPOE_TAG_IP_RT_ADD   0x0121
#define PPPOE_TAG_SVC_ERR     0x0201
#define PPPOE_TAG_AC_ERR      0x0202
#define PPPOE_TAG_GENERIC_ERR 0x0203 /*Generic-Error*/

几个常见的标签

0x0101 Service-Name

在这里插入图片描述

该标签指示跟随服务名称。 TAG_VALUE是一个不为NULL终止的UTF-8字符串。 当TAG_LENGTH为零时,此TAG用于指示任何服务可以接受的。 使用服务名称TAG的示例包括:指出ISP名称或服务等级或质量。

0x0102 AC-Name

在这里插入图片描述
此TAG表示紧随其后的字符串可唯一标识,所有其他特定的访问集中器单元。 有可能是商标,型号和序列号信息的组合,或者只是包装盒MAC地址的UTF-8格式。 该字符串不能以NULL终止。

0x0203 Generic-Error

在这里插入图片描述

该标签表示错误。 可以将其添加到PADO,PADR或发生不可恢复的错误且没有其他错误时的PADS数据包TAG是合适的。 如果有数据,则必须为UTF-8 解释错误性质的字符串。 这个字符串必须NOT NULL终止。

标签实现:

/* Dissect discovery protocol tags */
static void dissect_pppoe_tags(u_char *pppoe_data,int offset,int payload_length)
{
	int tagstart = 0;
	
	tagstart = offset;
	
	/*循环遍历,直到看到所有数据或找到列表结束标记*/
	while (tagstart <= payload_length - 2)
	{
		uint16_t poe_tag = ntohs(*(uint16_t*)(pppoe_data + tagstart));
		//printf("poe_tag 0x%.2X\n",poe_tag);
		tagstart += 2;
		uint16_t poe_tag_length = ntohs(*(uint16_t*)(pppoe_data + tagstart));
		//printf("poe_tag_length 0x%.2X\n",poe_tag_length);
		tagstart += 2;
		switch(poe_tag)
		{
			case  PPPOE_TAG_SVC_NAME:
			{
				if (poe_tag_length > 0)
				{
					char *pszSerName = (char*)malloc(1024);
					if (pszSerName != 0)
					{
						memcpy(pszSerName, pppoe_data + tagstart,poe_tag_length);
						pszSerName[poe_tag_length] = '\0';
						printf("Service-Name: %s\n",pszSerName);
					}						
				}

			}
			break;
			
			case  PPPOE_TAG_AC_NAME:
			{
				char *pszAcName  = (char*)malloc(1024);
				if (pszAcName != 0)
				{
					memcpy(pszAcName, pppoe_data + tagstart,poe_tag_length);
					pszAcName[poe_tag_length] = '\0';
					printf("AC-Name: %s\n",pszAcName);
				}
							
			}
			break;
				
			case  PPPOE_TAG_GENERIC_ERR:
			{
				char *pszGenError  = (char*)malloc(1024);
				if (pszGenError != 0)
				{
					memcpy(pszGenError, pppoe_data + tagstart,poe_tag_length);
					pszGenError[poe_tag_length] = '\0';
					printf("Generic-Error: %s\n",pszGenError);
				}
							
			}
			break;
			/*
				...
			*/	
			default:
				break;
			
		}
		
		tagstart += poe_tag_length;
	}
	
}

pppoe协议代码解析:

#include <sys/stat.h>
#include <sys/types.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <net/ethernet.h>
#include <sys/socket.h>
#include <netinet/in.h>
//pppoe
#include <linux/if.h>
#include <linux/if_pppox.h>
#include <linux/ppp_defs.h>

#include <pcap.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


#define PPP_LCP		0xc021	/* Link Control Protocol */
#define PPP_PAP		0xc023	/* Password Authentication Protocol */
#define PPP_IPCP	0x8021	/* Internet Protocol Control Protocol */
#define PPP_IPV6CP	0x8057	/* IPv6 Control Protocol */

#define PPPOE_TAG_EOL         0x0000
#define PPPOE_TAG_SVC_NAME    0x0101 /*Service-Name*/
#define PPPOE_TAG_AC_NAME     0x0102 /*AC-Name*/
#define PPPOE_TAG_HOST_UNIQ   0x0103
#define PPPOE_TAG_AC_COOKIE   0x0104
#define PPPOE_TAG_VENDOR      0x0105
#define PPPOE_TAG_CREDITS     0x0106
#define PPPOE_TAG_METRICS     0x0107
#define PPPOE_TAG_SEQ_NUM     0x0108
#define PPPOE_TAG_CRED_SCALE  0x0109
#define PPPOE_TAG_RELAY_ID    0x0110
#define PPPOE_TAG_HURL        0x0111
#define PPPOE_TAG_MOTM        0x0112
#define PPPOE_TAG_MAX_PAYLD   0x0120
#define PPPOE_TAG_IP_RT_ADD   0x0121
#define PPPOE_TAG_SVC_ERR     0x0201
#define PPPOE_TAG_AC_ERR      0x0202
#define PPPOE_TAG_GENERIC_ERR 0x0203 /*Generic-Error*/

/* Dissect discovery protocol tags */
static void dissect_pppoe_tags(u_char *pppoe_data,int offset,int payload_length)
{
	int tagstart = 0;
	
	tagstart = offset;
	
	/*循环遍历,直到看到所有数据或找到列表结束标记*/
	while (tagstart <= payload_length - 2)
	{
		uint16_t poe_tag = ntohs(*(uint16_t*)(pppoe_data + tagstart));
		//printf("poe_tag 0x%.2X\n",poe_tag);
		tagstart += 2;
		uint16_t poe_tag_length = ntohs(*(uint16_t*)(pppoe_data + tagstart));
		//printf("poe_tag_length 0x%.2X\n",poe_tag_length);
		tagstart += 2;
		switch(poe_tag)
		{
			case  PPPOE_TAG_SVC_NAME:
			{
				if (poe_tag_length > 0)
				{
					char *pszSerName = (char*)malloc(1024);
					if (pszSerName != 0)
					{
						memcpy(pszSerName, pppoe_data + tagstart,poe_tag_length);
						pszSerName[poe_tag_length] = '\0';
						printf("Service-Name: %s\n",pszSerName);
					}						
				}

			}
			break;
			
			case  PPPOE_TAG_AC_NAME:
			{
				char *pszAcName  = (char*)malloc(1024);
				if (pszAcName != 0)
				{
					memcpy(pszAcName, pppoe_data + tagstart,poe_tag_length);
					pszAcName[poe_tag_length] = '\0';
					printf("AC-Name: %s\n",pszAcName);
				}
							
			}
			break;
				
			case  PPPOE_TAG_GENERIC_ERR:
			{
				char *pszGenError  = (char*)malloc(1024);
				if (pszGenError != 0)
				{
					memcpy(pszGenError, pppoe_data + tagstart,poe_tag_length);
					pszGenError[poe_tag_length] = '\0';
					printf("Generic-Error: %s\n",pszGenError);
				}
							
			}
			break;
			/*
				...
			*/	
			default:
				break;
			
		}
		
		tagstart += poe_tag_length;
	}
	
}



/* Discovery protocol, i.e. PPP session not yet established */
static int dissect_pppoed(u_char *pppoe_data)
{
	int offset = 0;
	
	offset += 1;
	
	/* Start Decoding Here. */
	
	uint8_t pppoe_code = *(uint8_t*)pppoe_data+offset;
	offset += 1;
	
	/*skip Session ID*/
	offset += 2;
	/*Read length of payload*/
	uint16_t payload_len = ntohs(*(uint16_t*)(pppoe_data+offset));
	
	printf("payload_len %d\n",payload_len);
	offset += 2;
	/*Now dissect any tags*/
	
	if (payload_len > 0)
	{		
		dissect_pppoe_tags(pppoe_data,offset,payload_len);
	}
	
}


/* Session protocol, i.e. PPP session established */
static int dissect_pppoes(u_char *pppoe_data)
{
	int tagstart = 0;
	u_char code = 0;
	tagstart += 1;
		
	uint8_t pppoe_code = *(uint8_t*)pppoe_data+tagstart;
	
	tagstart += 1;
	
	
	tagstart += 2;/*skip Session ID*/
	/*Read length of payload*/
	uint16_t payload_len = ntohs(*(uint16_t*)(pppoe_data+tagstart));
	
	printf("payload_len %d\n",payload_len);
	tagstart += 2;/*Payload Length*/
	
	/*检索控制代码*/
	uint16_t cp_code = ntohs(*(uint16_t*)(pppoe_data+tagstart));
	printf("Protocol: 0x%X\n",cp_code);
	tagstart += 2;/*Point-to-Point Protocol*/
	/*构造一个包含ppp数据包*/

	switch(cp_code)
	{
		case PPP_LCP:
			
			break;
		case PPP_PAP:
			code = pppoe_data[tagstart];
			printf("auth_request 0x%x\n",code);
			if (code == 1)
			{
				tagstart += 1; /*code*/
				
				tagstart += 1;/*Identifier*/
				tagstart += 2;/*Length*/
				/*Data*/
				u_char peer_id_len = pppoe_data[tagstart];
				printf("Peer-ID-Lnegth: 0x%x\n",peer_id_len);
				tagstart += 1; /*Peer-ID-Lnegth*/
				char *pszPeer  = (char*)malloc(peer_id_len + 1);
				if (pszPeer != 0)
				{
					memcpy(pszPeer, pppoe_data + tagstart,peer_id_len);
					pszPeer[peer_id_len] = '\0';
					printf("Peer-ID: %s\n",pszPeer);
				}
				tagstart += peer_id_len; /*Peer-ID*/
				u_char Password_Length = pppoe_data[tagstart];
				printf("Password-Length: 0x%x\n",Password_Length);
				tagstart += 1; /*Password-Length*/
				char *pszPassword  = (char*)malloc(Password_Length + 1);
				if (pszPassword != 0)
				{
					memcpy(pszPassword, pppoe_data + tagstart,Password_Length);
					pszPassword[Password_Length] = '\0';
					printf("Password: %s\n",pszPassword);
				}
				
			}
			break;
		case PPP_IPCP:
			code = pppoe_data[tagstart];
			printf("Configuration request 0x%x\n",code);
			if (code == 1)
			{
				tagstart += 1; /*code*/
				
				tagstart += 1;/*Identifier*/
				tagstart += 2;/*Length*/
				/*Options*/
				
				u_char type = pppoe_data[tagstart];
				printf("type 0x%x\n",type);
				tagstart += 1; /*type*/
				u_char length = pppoe_data[tagstart];
				printf("length 0x%x\n",length);
				tagstart += 1; /*length*/
				
				in_addr ip;
				memcpy(&ip.s_addr, pppoe_data + tagstart, sizeof(ip.s_addr));
				printf("IP Address: %s\n",inet_ntoa(ip));
				
			}
			break;
		case PPP_IPV6CP:
			
			code = pppoe_data[tagstart];
			printf("Interface Identifier 0x%x\n",code);
			if (code == 1)
			{
				tagstart += 1; /*code*/
				
				tagstart += 1;/*Identifier*/
				tagstart += 2;/*Length*/
				/*Options*/
				
				u_char type = pppoe_data[tagstart];
				printf("type 0x%x\n",type);
				tagstart += 1; /*type*/
				u_char length = pppoe_data[tagstart];
				printf("length 0x%x\n",length);
				tagstart += 1; /*length*/
				
				struct in6_addr ip_v6;
				
				memcpy(&ip_v6.s6_addr, pppoe_data + tagstart, sizeof(ip_v6.s6_addr));
				char ip6[128] = {0};
				printf("Interface Identifier: %s\n",inet_ntop(AF_INET6, (void *)&ip_v6, ip6, 128));
			}
			
			
			break;	
			
		default:
			break;	
		
	}
	
}

static bool is_pppope(struct ether_header *pEther)
{
	
	printf("info pppoe\n");
	struct pppoe_hdr *pppoe_h;
	pppoe_h = (pppoe_hdr *)(pEther + 1);
    //PPPoED
    if( ntohs(pEther->ether_type) == 0x8863 && pppoe_h->code == PADT_CODE)
    {
        return true;
    }
    //PPPoES
    if( ntohs(pEther->ether_type) == 0x8864 )
    {
		/*
        if(ntohs(pppoe_h->type) == PPP_PAP || ntohs(pppoe_h->type) == PPP_CHAP)
        {
            return true;
        }
        if(ntohs(pppoe_h->type) == PPP_IPCP || ntohs(pppoe_h->type) == PPP_IPV6CP)
        {
            return true;
        }
		*/
		return true;
    }
    return false;
	
}

int pkt_number = 0;
void ace_pcap_hand(u_char *par, struct pcap_pkthdr *hdr, u_char *data)
{	

	int i = 0;
    pkt_number++;
    struct ether_header *pEther = (struct ether_header *)data;/*以太网帧头*/
	u_char *pppoe_data =  (u_char *)(pEther + 1);
	
	printf("\n");
	printf("pkt_number %d\n",pkt_number);
	printf("Destination: ");
	for (i = 0; i <6; i++)
	{	
		printf("%.2X",pEther->ether_dhost[i]);
			if (i != 5)
				printf(":");
	}
	printf("\n");
	printf("Source: ");
	for (i = 0; i <6; i++)
	{	
		printf("%.2X",pEther->ether_shost[i]);
			if (i != 5)
				printf(":");
	}
	printf("\n");
	printf("type 0x%X\n",ntohs(pEther->ether_type));
	
	//printf("0x%.2X,0x%.2X,0x%.2X \n",pppoe_data[0],pppoe_data[1],pppoe_data[2]);
	if (!is_pppope(pEther))
		return ;
	
	if (ntohs(pEther->ether_type) == 0x8864)			
		dissect_pppoes(pppoe_data);
	else if (ntohs(pEther->ether_type) == 0x8863)	
		dissect_pppoed(pppoe_data);
	else
	{
		
	}

}

int main(int argc, char* argv[])
{
    char errbuf[1024];
    pcap_t *desc = 0;

    char *filename = argv[1];
    if (argc != 2)
    {
        printf("usage: ./dissect_pppoe [pcap file]\n");
        return -1;
    }

    printf("ProcessFile: process file: %s\n", filename);
    if ((desc = pcap_open_offline(filename, errbuf)) == NULL)
    {   
        printf("pcap_open_offline: %s error!\n", filename);
        return -1; 
    }   

    pcap_loop(desc, pkt_number, (pcap_handler)ace_pcap_hand, NULL);
    pcap_close(desc);
    return 0;
}

pppoe:
在这里插入图片描述
ppp:

在这里插入图片描述

总结

点对点协议(PPP)和以太网点对点协议(PPPoE)是允许两个网络实体或点之间进行数据通信的网络协议。

在两种协议的整个文档中,点都称为节点,计算机或主机。协议的设计相似,但主要区别在于PPPoE封装在以太网帧中。两种协议都存在于支持包括IPv4和IPv6在内的网络层协议的网络访问层(也称为数据链路层)。

参考:https://www.rfc-editor.org/rfc/rfc2516.txt

在这里插入图片描述

欢迎关注微信公众号【程序猿编码】,添加本人微信号(17865354792),回复:领取学习资料。或者回复:进入技术交流群。共同学习!网盘资料有如下:

在这里插入图片描述

标签:协议,tagstart,以太网,TAG,printf,PPPOE,pppoe,PPPoE
来源: https://blog.csdn.net/chen1415886044/article/details/112119992

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有