以太网点对点协议之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协议代码解析:


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在内的网络层协议的网络访问层(也称为数据链路层)。

欢迎关注微信公众号【程序猿编码】,需要PPPOE源代码和报文的添加本人微信号(17865354792)

  • 1
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值