以太网点对点协议之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