PPP(Point-to-Point Protocol点到点协议),属于数据链路层协议,这种链路提供全双工操作,并按照顺序传递数据包。
PPP协议是一个协议集包含:LCP(Link Control Protocol)链路控制协议,和NCP(Network Control Protocol)网络控制协议。
PPP协议经历5个阶段:初始化阶段、LCP协商阶段(包含认证)、NCP(IPCP)协商阶段、PPP会话阶段、网络终止阶段。
PPP连接建立最主要的是要经过三个阶段:
第一阶段:LCP连接协商
在这个阶段,将对基本的通讯方式进行选择。链路两端设备通过LCP向对方发送LCP数据报配置请求(Configure-Request),对方同意接收后双方互发LCP数据报配置应答(Configure-Ack )。一旦一个配置成功信息包(Configure-Ack packet)被发送且被接收,就完成了交换,进入LCP开启状态。
第二阶段:CHAP密码认证
在这个阶段,客户端会将自己的身份发送给远端的接入服务器。服务器向用户发PPP CHAP安全性认证挑战,接着用户给服务器送PPP CHAP安全性认证响应,服务器再向用户发送 PPP CHAP安全性认证成功。
该阶段使用一种安全验证方式避免第三方窃取数据或冒充远程客户接管与客户端的连接。在认证完成之前,禁止从认证阶段前进到网络层协议阶段。如果认证失败,认证者应该跃迁到链路终止阶段。
第三阶段:NCP网络层协议配置
认证阶段完成之后,将调用在链路创建阶段(阶段一)选定的各种网络控制协议(NCP )。选定的NCP解决链路之上的高层协议问题,例如,在该阶段IP控制协议(IPCP)可以向拨入用户分配动态IP地址。 在这个阶段,先是用户向服务器发送PPP NCP网络控制数据包(网络协议配置,要求服务器提供IP地址和DNS,信息),接着服务器向用户发送配置请求PPP NCP网络控制数据包(为用户分配IP地址),用户向服务器发送配置应答PPP NCP网络控制数据包(接受所分配的IP地址),最后服务器向用户发送配置应答PPP NCP网络控制数据包(同意用户的IP地址和DNS地址)。
这样,经过三个阶段以后,一条完整的链路就建立起来了,用户即可向服务器发送IP数据包。
什么是PDP上下文:
4G模块首先要请求网关分配一个IP地址(称为PDP地址,可以看做是移动网关内部的私有地址),然后才能经由网关和外部数据网络通信 这个请求网关分配地址的过程称为激活PDP ,PDP地址就像开通有线电话时,开通工单上的电话号码,而PDP上下文就是这张电话开通工单,上面不仅有分配给你的电话号码,还有这部电话对应的其他属性、其他功能的信息。PDP上下文是一个结构,而PDP地址只是结构中的一个成员而已,除此之外,还包括QoS、APN等,这些都是PDP上下文的内容
在PDP上下文中,有一个重要的参数APN,APN是4G模块用来告知网关要访问哪种外部数据网络(外部分组数据网,包括企业内部网、Internet、WAP网站、行业内部网等);还是以刚才的开通电话为例,开通时要求“仅支持省内电话呼入呼出”、“仅支持国内电话呼入呼出”、“可全球呼入呼出”这个就相当于是电话。
协议(2字节):
0xC021 LCP协议
0xC023 PAP协议
0x8021 IPCP协议
0x0021 IP协议
编码(1字节):
0x01 配置请求(Req)
0x02 接受配置请求 (Ack)
0x03 配置请求接受,其他拒绝 (Nak)
0x04 配置请求不认识或者不被接受 (Rej)
0x05 终止链接
0x06 终止确认
另外
PPP数据帧每一帧都以标识字符0x7E开始和结束;
由于标识字符的值是0x7E,因此当该字符出现在信息字段中时,PPP需要对它进行转义;
当PPP使用异步传输时,它把转义字符定义为:0x7D,并使用字节填充RFC-1662标准;
字节填充RFC-1662标准规定如下:
把信息字段中出现的每一个0x7E字符转变成字节序列(0x7D,0x5E);
若信息字段中出现一个0x7D的字节(即出现了与转义字符相同的比特组合),则把0x7D转义成两个字节序列(0x7D,0x5D);
若信息字段中出现ASCII码的控制字符(即数值小于0x20的字符),则在该字符前面加入一个0x7D字节,同时将该字符的编码加以改变。
LCP可以参考RFC1661, RFC1661中文版:
http://www.doczj.com/doc/9f1242566-25.html
LCP协商过程:
(LCP报文中需要 用转义字符,但是随后的PAP,CHAP认证报文和NCP报文却不需要加转义字符)
校验码计算:http://www.ip33.com/crc.html
NCP有很多种,如IPCP、BCP、IPv6CP,最为常用的是IPCP(Internet Protocol Control Protocol)协议。NCP的主要功能是协商PPP报文的网络层参数,如IP地址,DNS Server IP地址,WINS Server IP地址等。PPPoE用户主要通过IPCP来获取访问网络的IP地址或IP地址段。 目前服务端默认只支持NCP中的IPCP协议。
IPCP协议:
IPCP控制协议主要是负责完成IP网络层协议通信所需配置参数的选项协商,负责建立,使能和中止IP模块。IPCP在运行的过程当中,主要是完成点对点通信设备的两端动态的协商IP地址。IPCP包在PPP没有达到网络层协议阶段以前不能进行交换,如果有IPCP包在到达此阶段前到达会被抛弃。
1.代码域1字节长,标识域1字节长,长度域2字节长。
2.IPCP是在网络层协议阶段协商配置参数选项,code字段为0x8021
3.代码域字段。LCP共包括十几种报文,而IPCP只包括7种报文,但它的报文类型只是LCP数据报文的一个子集(只有LCP代码域从1到7这七种报文:Config-Request,Config Ack,Config-Nak,Config-Reject,Terminate-Request,Terminate-Ack和Code
Reject),而且实际的数据报文交换过程中链路终止报文一般而言是不在网络协议阶段使用的。
PPP_Packet.h
/* ***************************************************************
* Filename: Packet.h
* @Description:
* Packet Creat Check
* @Author: ybLin
* ***************************************************************/
#ifndef __PPP_PACKET_H__
#define __PPP_PACKET_H__
#include
#include "crc.h"
#include
#define PPP_FRAME_FLAG 0x7E //标识字符
#define PPP_FRAME_ESC 0x7D //转义字符
#define PPP_FRAME_ENC 0x20 //编码字符
#define MAX_RECV_PKT_SIZE 256
#define MIN_RECV_PKT_SIZE 12
#define PROTOCOL_LCP 0xC021 //LCP协议
#define PROTOCOL_IPCP 0x8021 //NCP协议:IPCP
#define PROTOCOL_IP 0x0021 //IP协议
#define PROTOCOL_PAP 0xC023 //PAP认证
extern BYTE g_LcpFirstReq[256];
extern BYTE g_TermReq[256];
extern BYTE g_NcpFirstReq[256];
extern std::string HexToString(const BYTE *pBuffer, size_t iBytes);
extern void DectoHex(int dec, char *hex, int length);
extern unsigned long HextoDec(const unsigned char *hex, int length);
extern CString Ascii2Hex(CString strASCII);
extern char Char2Hex(char ch);
extern int String2Hex(CString str, char* SendOut);
//执行过程
typedef enum PPP_STATE
{
PPP_STATE_INIT = 0,
PPP_STATE_LCP_PERIOD,
PPP_STATE_LCP_PASS,
PPP_STATE_NCP_PERIOD,
PPP_STATE_NCP_NAK,
PPP_STATE_PPP_PASS,
PPP_STATE_IP_START,
PPP_STATE_TERM_LINK
}PPP_STATE_E;
//编码值
typedef enum PPP_CODE
{
PPP_CODE_REQ = 1, //配置请求
PPP_CODE_ACK, //接受配置
PPP_CODE_NAK, //配置请求接受,其他拒绝
PPP_CODE_REJ, //配置请求不认识,或不被接受
PPP_CODE_TERM_LINK, //终止链接
PPP_CODE_TERM_ACK, //终止确认
PPP_CODE_CODE_REJ,
PPP_CODE_PROTOCAL_REJ,
PPP_CODE_ECHO_REQ,
PPP_CODE_ECHO_REP,
PPP_CODE_DISCARD_REQ,
PPP_CODE_IDENTIFICATION,
PPP_CODE_TIME_REM
}PPP_CODE_E;
//报文解析返回
enum PPP_OPERATE_STATE
{
PPP_OPERATE_ERROR = -1,
PPP_OPERATE_NORMAL,
PPP_OPERATE_NEED_SEND
};
//选项值
typedef struct PPP_OPTION
{
BYTE bType;
BYTE bLength;
BYTE bData[64];
BOOL bReject;
}PPP_OPTION_T;
//IP头
typedef struct IP_HEADER
{
BYTE Version; //4bits
BYTE IHL; //4bits
BYTE Service;
unsigned short TotalLength;
unsigned short Identification = 0x0000;
BYTE FLAG;
unsigned short FlagFrag;
BYTE TTL;
BYTE protocol;
BYTE HeaderSum;
BYTE SrcIP[4];
BYTE DesIP[4];
}IP_HEADER_T;
//ICMP头
typedef struct ICMP_HEADER
{
BYTE type;
BYTE code;
unsigned short sum;
unsigned short identifier;
unsigned short sequence = 0x0000;
}ICMP_HEADER_T;
//IP
#define IP_ICMP 0x01
#define IP_TCP 0x06
#define IP_UDP 0x11
//ICMP
#define ICMP_PING 0x08
#define ICMP_PINGREPLY 0x00
class CPacket
{
public:
CPacket();
virtual ~CPacket();
//初始化
void InitPacket();
//解析报文
int Parsepkt(BYTE *pPkt, int nLen);
//检测配置选项
void CheckOption(WORD wProType, BYTE CodeVal, BYTE* pOption);
//创建PPP报文
void CreatePPPPkt(WORD wProType, BYTE CodeVal, BYTE* pPkt, bool bActive = FALSE);
//创建IP报文
void CreateIpPkt(BYTE protocal, BYTE type, BYTE *temp);
private:
//字符编码
int CharacterEncode(int iLen);
//转义编码
int TransferEncode(unsigned char *pInPkt, int nInLen, unsigned char *pOutPkt);
//转义解码
int TransferDecode(unsigned char *pInPkt, int nInLen, unsigned char *pOutPkt);
//日志输出
void WriteLog(CString temp);
public:
WORD wProtocolType; //协议类型 2个字节
BYTE PktID; //包 ID
BOOL bReject; //判断包是 ACK 或者 REJ
BYTE CurState; //PPP状态
BYTE SrcIP[4]; //源动态IP
int nOptionNum; //配置选项数量
BYTE PacketTx[256]; //发送包
BYTE PacketTx1[256]; //发送包
BYTE PacketRx[256]; //接受包
int TxLen; //发送包长度
int RxLen; //接受包长度
BOOL bRecPing; //是否收到ping回复
PPP_OPTION_T option[8]; //选项值
IP_HEADER_T IP_header;
ICMP_HEADER_T ICMP_header;
int m_nNcpAckNum;
CCRC m_crc;
};
#endif
PPP_Packet.cpp
/* ***************************************************************
* Filename: Packet.cpp
* @Description:
* Packet Creat Check
* @Author: ybLin
* ***************************************************************/
#include "stdafx.h"
#include "PPP_Packet.h"
#include "Common.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
int auth_skipped=1;
int block_ipcp_req= 0;
BYTE g_LcpFirstReq[256] = {0x7E, 0xFF, 0x03, 0xC0, 0x21, 0x01, 0x01, 0x00, 0x0A, 0x02, 0x06, 0x00, 0x00,
0x00, 0x00, 0x58, 0x7B, 0x7E};
BYTE g_NcpFirstReq[256] = {0x7E, 0xFF, 0x03, 0x80, 0x21, 0x01, 0x03, 0x00, 0x0A, 0x03, 0x06, 0x00,
0x00, 0x00, 0x00, 0xE9, 0xB3, 0x7E};
BYTE g_TermReq[256] = {0x7E, 0xFF, 0x03, 0xC0, 0x21, 0x05, 0x01, 0x00, 0x04, 0x3D, 0xC7, 0x7E};
char hextbl[] = { '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' };
//Common Api
std::string HexToString(const BYTE *pBuffer, size_t iBytes)
{
std::string result;
for (size_t i = 0; i < iBytes; i++)
{
BYTE c ;
BYTE b = pBuffer[i] >> 4;
if (9 >= b)
{
c = b + '0';
}
else
{
c = (b - 10) + 'A';
}
result += (TCHAR)c;
b = pBuffer[i] & 0x0f;
if (9 >= b)
{
c = b + '0';
}
else
{
c = (b - 10) + 'A';
}
result += (TCHAR)c;
if (i != (iBytes-1))
result += " ";
}
return result;
}
void DectoHex(int dec, char *hex, int length)
{
for(int i=length-1; i>=0; i--)
{
hex[i] = (dec%256)&0xFF;
dec /= 256;
}
}
unsigned long HextoDec(const unsigned char *hex, int length)
{
unsigned long rslt = 0;
for(int i=0; i= '0') && (ch <= '9'))
return ch - 0x30;
if ((ch >= 'A') && (ch <= 'F'))
return ch - 'A' + 10;
if ((ch >= 'a') && (ch <= 'f'))
return ch - 'a' + 10;
else
return(-1);
}
//字符串转换为16进制数据
int String2Hex(CString str, char* SendOut)
{
int hexdata, lowhexdata;
int hexdatalen = 0;
int len = str.GetLength();
for (int i = 0; i < len;)
{
char lstr, hstr = str[i];
if (hstr == ' ' || hstr == '\r' || hstr == '\n')
{
i++;
continue;
}
i++;
if (i >= len)
break;
lstr = str[i];
hexdata = Char2Hex(hstr);
lowhexdata = Char2Hex(lstr);
if ((hexdata == 16) || (lowhexdata == 16))
break;
else
hexdata = hexdata * 16 + lowhexdata;
i++;
SendOut[hexdatalen] = (char)hexdata;
hexdatalen++;
}
return hexdatalen;
}
//Packet
CPacket::CPacket()
{
InitPacket();
}
CPacket::~CPacket()
{
}
void CPacket::InitPacket()
{
CurState = PPP_STATE_INIT;
TxLen = 0;
bReject = FALSE;
PktID = 0x01;
nOptionNum = 0;
for(int i=0; i<8; i++)
{
option[i].bReject = TRUE;
memset(option[i].bData, 0x0, 64);
}
memset(PacketTx,0x0,256);
memset(SrcIP,0x00,4);
}
int CPacket::Parsepkt(BYTE *pPkt, int nLen)
{
BYTE CodeVal;
char tempbuf[100];
memset(PacketRx, 0x00, 256);
TxLen = 0;
int nRet = PPP_OPERATE_NORMAL;
if(nLen < MIN_RECV_PKT_SIZE)
{
ffprint("nLen < MIN_RECV_PKT_SIZE nLen:%d", nLen);
nRet = PPP_OPERATE_ERROR;
return nRet;
}
RxLen = TransferDecode(pPkt, nLen, PacketRx);
if(RxLen < MIN_RECV_PKT_SIZE)
{
ffprint("RxLen < MIN_RECV_PKT_SIZE RxLen:%d", RxLen);
nRet = PPP_OPERATE_ERROR;
return nRet;
}
std::string hexStr2 = HexToString(PacketRx, RxLen);
ffprint("Parsepkt decode hexStr:%s", hexStr2.c_str());
wProtocolType = PacketRx[3]*256 + PacketRx[4];
switch(wProtocolType)
{
case PROTOCOL_LCP:
{
CodeVal = PacketRx[5]; //获取编码值
PktID = PacketRx[6];
switch(CodeVal)
{
case PPP_CODE_REQ:
CheckOption(wProtocolType, CodeVal, PacketRx);
if(bReject == TRUE)
{
CodeVal = PPP_CODE_REJ;
}
else
{
CodeVal = PPP_CODE_ACK;
}
CreatePPPPkt(wProtocolType, CodeVal, PacketRx);
nRet = PPP_OPERATE_NEED_SEND;
break;
case PPP_CODE_ACK:
if(CurState == PPP_STATE_LCP_PASS)
{
//无认证,直接发送NCP请求
CurState = PPP_STATE_NCP_PERIOD;
CreatePPPPkt(PROTOCOL_IPCP, PPP_CODE_REQ, g_NcpFirstReq, TRUE);
nRet = PPP_OPERATE_NEED_SEND;
m_nNcpAckNum = 0;
}
break;
case PPP_CODE_NAK:
break;
case PPP_CODE_REJ:
break;
default:
break;
}
break;
}
case PROTOCOL_IPCP: //无转义
CodeVal = PacketRx[5];
PktID = PacketRx[6];
switch(CodeVal)
{
case PPP_CODE_REQ:
ffprint("PROTOCOL_IPCP PPP_CODE_REQ");
CurState = PPP_STATE_NCP_PERIOD;
CreatePPPPkt(wProtocolType, PPP_CODE_ACK, PacketRx);
nRet = PPP_OPERATE_NEED_SEND;
m_nNcpAckNum++;
break;
case PPP_CODE_ACK: //最终受到确认后,NCP通过
ffprint("PROTOCOL_IPCP PPP_CODE_ACK");
if (CurState < PPP_STATE_PPP_PASS)
CurState = PPP_STATE_PPP_PASS;
m_nNcpAckNum = 0;
break;
case PPP_CODE_NAK:
ffprint("PROTOCOL_IPCP PPP_CODE_NAK");
CheckOption(wProtocolType, CodeVal, PacketRx);
if (CurState < PPP_STATE_NCP_NAK && bReject == false)
CurState = PPP_STATE_NCP_NAK;
CreatePPPPkt(wProtocolType, PPP_CODE_REQ, PacketRx);
nRet = PPP_OPERATE_NEED_SEND;
break;
default:
break;
}
break;
/*
case PAP:
CodeVal=pPkt[5];
PktID=pPkt[6];
switch(CodeVal){
case REQ:
break;
case ACK:
CurState=PAPPASS;
memset(PacketTx,0x00,256);
WriteLog("dingding: after got pap ack from mt, set PAPPASS,then ipcp lcp req to mt");
makepacket(IPCP,REQ,PacketTx);
break;
case NAK:
break;
default:
break;
}//end switch
break;//end PAP
*/
case PROTOCOL_IP:
IP_header.protocol = PacketRx[14];
switch(IP_header.protocol)
{
case IP_ICMP:
ICMP_header.type = PacketRx[25];
ffprint("PROTOCOL_IP protocol:%02x type:%02x", IP_header.protocol, ICMP_header.type);
switch (ICMP_header.type)
{
case ICMP_PING:
//CreateIpPkt(IP_ICMP, ICMP_PINGREPLY, pPkt+5);
break;
case ICMP_PINGREPLY:
bRecPing = true;
break;
default:
break;
}
break;
case IP_UDP:
break;
default:
break;
}
break;
default:
break;
}
return nRet;
}
void CPacket::CheckOption(WORD wProType, BYTE CodeVal, BYTE* pOption)
{
WORD wSize, wStart;
int i=0, j=0, k;
bReject = FALSE;
wStart = 9; //数据位从这里开始
wSize = pOption[7]*256 + pOption[8] + 8; //8:length+framebegin(3)+protocol(2)+checksum(2)+frameend(1)
if (wSize > MAX_RECV_PKT_SIZE - 8) //truncate packet if larger than buffer
wSize = MAX_RECV_PKT_SIZE - 8;
//获取配置项
for(k=0; k<8; k++)
{
option[k].bReject = TRUE;
memset(option[k].bData, 0x0, 64);
}
while(wStart < wSize-3)
{
option[j].bType = pOption[wStart++];
option[j].bLength = pOption[wStart++];
for (i = 0; i
https://download.csdn.net/download/linyibin_123/87759183