PPPOE 协议
PPPOE 协议是明文传输,抓捕是可以看到Data中的账号和密码的 是点对点 链路协议
//PPPOE 是不绑定端口的而是绑定网卡
// AF_PACKET链路层
// ETHER_TYPE_DISCOVERY 协议码
socket(AF_PACKET,SOCK_RAW,htons(ETHER_TYPE_DISCOVERY));
PPPOE 分为4阶段
1. discovery (发现)阶段
discovery 阶段
- 客户端广播一PADI(I = Initiation)请求,查找网络中的服务器 广播包
- 服务器端响应一PADO(O = Offer),将自己的一些信息告知客户端
- 客户端向此服务器发送一PADR(R = Request),请求会话号
服务器端响应一PADS(S = Session-confirmation),将分配的会话号告知对方 返回请求的会话号
**至此,PPPoE会话建立。后续可以进行PPP的协商了**
绑定网卡
- 制定协议类型(没有现成的类型,协议自己根据动态包来自己写)
协议码 ETHER_TYPE_DISCOVERY 0x8863
- 发送PADI
- 收取PADO
- 发送PADR
- 收取PADS
发现阶段查找网络中的服务器
discovery 是绑定网卡而不是绑定IP
struct ifreq 网卡结构体
- 头文件 #include
ioctl() 绑定 网卡
绑定 获取网卡的操作句柄 操作权 就是fd 网卡的fd存在
ifreq 的 ifr_ifindex中
头文件: #include
参数1:socketfd
参数2:cmd 要设置的是什么东西 SIOCGIFINDEX
参数3:ifreq 网卡结构体
返回值:-1 错误
cmd: https://blog.csdn.net/lin364812726/article/details/18814067
struct ifreq ifr;
int fd = socket(AF_PACKET,SOCK_RAW,htons(ETHER_TYPE_DISCOVERY));
// AF_PACKET 链路层协议
ioctl(fd,SIOCGIFINDEX,&ifr);
ifr.ifr_ifindex; //存放 网卡操作句柄
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// sockaddr_ll 链路层专用 类型
srand((unsigned int)getpid()); //ready for rand
unsigned short int count_client;
struct Connection_info infos[clients];
memset(infos,0,sizeof(infos));
ifindex=get_ifindex(NIC);
//创建discovery阶段的socket
discovery_socket=socket(AF_PACKET,SOCK_RAW,htons(ETHER_TYPE_DISCOVERY));
//创建session阶段的socket
session_socket=socket(AF_PACKET,SOCK_RAW,htons(ETHER_TYPE_PPP_SESSION));
if (discovery_socket == -1 || session_socket == -1) {
printf("error in creating socket!\n%s\n",strerror(errno));
exit(1);
}
// discovery 阶段的 socket
struct sockaddr_ll sockaddr_used_to_bind_dis;
memset(&sockaddr_used_to_bind_dis,0,sizeof(sockaddr_used_to_bind_dis));
sockaddr_used_to_bind_dis.sll_protocol=ETHER_TYPE_DISCOVERY;//协议类型
sockaddr_used_to_bind_dis.sll_ifindex=ifindex; // 网卡句柄
if (bind(discovery_socket,(const struct sockaddr *)&sockaddr_used_to_bind_dis),sizeof(sockaddr_used_to_bind_dis)==-1) {
printf("failed in binding discovery socket\n%s\n",strerror(errno));
exit(1);
}
// session 阶段的 socket
struct sockaddr_ll sockaddr_used_to_bind_ses;
memset(&sockaddr_used_to_bind_ses,0,sizeof(sockaddr_used_to_bind_ses));
sockaddr_used_to_bind_ses.sll_protocol=ETHER_TYPE_PPP_SESSION;//当前协议类型
sockaddr_used_to_bind_ses.sll_ifindex=ifindex; // 网卡句柄
if (bind(session_socket,(const struct sockaddr *)&sockaddr_used_to_bind_ses),sizeof(sockaddr_used_to_bind_ses)==-1) {
printf("failed in binding session socket\n%s\n",strerror(errno));
exit(1);
}
例: 获取网卡 fd 固定写法
int set_promisc (char *device_name) { //输入网卡名
int sock=socket(AF_PACKET,SOCK_RAW,htons(ETHER_TYPE_DISCOVERY));
struct ifreq ifr;
memset(&ifr,0,sizeof(ifr));
strncpy (ifr.ifr_name,device_name, sizeof(ifr.ifr_name) - 1);
ifr.ifr_name[sizeof(ifr.ifr_name)-1]='\0';
ioctl(sock,SIOCGIFFLAGS,&ifr);
ifr.ifr_flags |= IFF_PROMISC;
ioctl(sock,SIOCSIFFLAGS,&ifr);
close(sock);
return 0;
}
制定协议类型
PADI / PADO / PADR / PADS类型结构体
是根据 抓到的包来定义结构体类型
#define CODE_OF_PADI 0x09
#define CODE_OF_PADO 0x07
#define CODE_OF_PADR 0x19
#define CODE_OF_PADS 0x65
struct pppoe_tags{
unsigned short tag_type; //数据类型
unsigned short tag_len; //数据长度
char tag_data[0]; //主机的唯一标识
}
每个TAG都符合TLV结构,即Type-Length-Value,常见的有TAG有:
Service-Name
Type=0101,表示PPPoE服务的名字;服务器提供的服务名字必须要和Client相同。
但如果Service-Name的Length为0,就表示任何提供的PPPoE服务都可以接受
// 单单不带 ppope头的 discorvey 结构体
struct pppoe_discovery{
unsigned char type:4;
unsigned char version:4;
unsigned char code; //协议类型
unsigned short session; //
unsigned short size; //长度
struct pppoe_tags tag[0]; //主机的唯一标识
}
struct pppoe_packet 总 discovery 协议类型
就是 一个 pppoe头 加上 PADI / PADO 类型结构体
// 当前阶段 值
#define CODE_OF_PADI 0x09 //第一个PADI是广播包
#define CODE_OF_PADO 0x07
#define CODE_OF_PADR 0x19
#define CODE_OF_PADS 0x65
struct pppoe_packet {
// mac head
char eth_dst_mac[6]; //目的MAK地址
char eth_src_mac[6]; //源MAK地址
unsigned short eth_type; //类型
//pppoe head struct pppoe_discovery_PADI
char pppoe_type:4;
char pppoe_ver:4;
unsigned char pppoe_code; // 协议类型
unsigned short pppoe_session_id;// 网络 序号
unsigned short pppoe_length; // 网络 序号 长度
//pppoe data 最大的字符 - PPPOE的头长度 6
char payload[MTU-PPPOE_HEADER_LEN];
//char date[1]; // 一空间的指针 可以指向其他空间
};
struct PPPOE_TAG PPPOE 的PADI阶段时的 PPPoE Tags 数据
struct PPPOE_TAG {
unsigned short TAG_TYPE; // TAG的类型
unsigned short TAG_LENGTH;//TAG的长度
unsigned char TAG_VALUE[MTU-PPPOE_HEADER_LEN-2];
};
2. Session (会议) 阶段
PPPP 主头
- LCP协商 链路控制协议此阶段主要是协商链路的一些参数 以及后续认证时使用的协议等
认证 此阶段服务器端将验证客户端的合法性 最常见的两种就是PAP和CHAP
- PAP认证:发送的认证信息是明文,可以通过抓包工具看到用户名、密码
- CHAP认证:发送的认证信息是密文,抓包工具无法解析出来真正的用户名、密码
- IPCP协商 此阶段进行IP、DNS、WINS等的协商
- 数据传输 上述任一阶段失败都会导致协议终止。如果都成功,则可以开始进行IP层的通信了
Session 阶段和 discorvey 阶段的包是不一样的 要使用不同的结构体 和不同的socket
Session LCP 阶段的协议
LCP阶段发送的结构体和结构体中携带的数据结构体
struct ppp_from_for_pppoe{
unsigned short Protocol;
char Information[1]; //当前结构体的有效数据
}
struct LCP_packet{
unsigned char Code;
unsigned char Identifier;
unsigned short Length;
char Date[1]; //当前结构体的有效数据
// 或:char Date[最大传输单元 - PPPoE头长度 - 上面类型的大小];
// 这样子写就不用自己动态去创建空间 就会自带空间
}
struct MRUBAK{
unsigned char Type;
unsigned char Length;
unsigned short varu;
}
认证阶段 传输账号名 密码(明文传输)
session阶段
- 因为不知道 要进行多少次的 LCP CHAP IPCP 的通信协商 所以在同一个函数中判断
void session (struct Connection_info *my_connection)
{ //判断 discovery 阶段完成 并且 创建一个子进程 去完成之后的 session阶段
// 父进程 只去做 discovety阶段的创建
if (my_connection->discovery_succeed && fork()==0)
{
// send_CFG_REQ() 发送 session 的第一个包(PPP LCP)
unsigned char LCP_ID=send_CFG_REQ(my_connection);
fd_set readable;
FD_ZERO(&readable);
FD_SET(session_sockeFt,&readable);
while(1)
{
struct timeval tv;
tv.tv_sec=TIME_OUT_SESSION;
tv.tv_usec=0;
int count;
FD_ZERO(&readable);
FD_SET(session_socket,&readable);
count=select(session_socket+1,&readable,NULL,NULL,&tv);
if(count==-1) {
printf("error in selecting!\n%s\n",strerror(errno));
exit(1);
}
if(count==0) {
printf("no packet hits the session socket!\n");
exit(1);
}
if(count>0)
{
struct pppoe_packet buff;
memset(&buff,0,sizeof(buff));
ssize_t recv_count;
struct ppp_frame_for_pppoe *ppp_frame=(struct ppp_frame_for_pppoe *)buff.payload;
recv_count=recv(session_socket,&buff,sizeof(buff),0);
if(recv_count==-1)
{
printf("error in receiving packets from session socket!\n%s\n",strerror(errno));
exit(1);
}
// 判断session_id 是否为本机发送的 session_id
if(buff.pppoe_session_id!=my_connection->pppoe_session_id)
{
continue;
}
// 判断发送过来的包是否为 LCP协议
if(ppp_frame->Protocol_Field==htons(LCP))
{
//处理LCP协议
LCP_handle(my_connection,&buff,LCP_ID);
}
// 判断是否为 CHAP协议
if(ppp_frame->Protocol_Field==htons(CHAP))
{ // 判断处理 CHAP协议是否成功
if(CHAP_handle(my_connection,&buff)==1)
{ // 发送 IPCP
send_IPCP_REQ(my_connection);
}
}
// 判断是否为 IPCP协议
if(ppp_frame->Protocol_Field==htons(IPCP))
{
IPCP_handle(my_connection,&buff);
}
}
}
}
}
处理 session 阶段的 LCP 包
// 处理 session 阶段的 LCP 包
int LCP_handle (struct Connection_info *my_connection,struct pppoe_packet *buff,unsigned char LCP_ID)
{
struct ppp_frame_for_pppoe *ppp_frame=(struct ppp_frame_for_pppoe *)buff->payload;
struct LCP_packet *lcp_packet=(struct LCP_packet *)ppp_frame->Information;
if(lcp_packet->Code==0x02 && lcp_packet->Identifier==LCP_ID) {
//we got a configure ACK!
return 0;
}
// 解析
if(lcp_packet->Code==0x09) {
lcp_packet->Code=0x0a;
unsigned char temp[MAC_LEN];
// 拷贝源MAC地址和目的MAC地址
memcpy(temp,buff->eth_src_mac,MAC_LEN);
memcpy(buff->eth_src_mac,buff->eth_dst_mac,MAC_LEN);
memcpy(buff->eth_dst_mac,temp,MAC_LEN);
buff->pppoe_length=htons(10); // 修改 协议类型
lcp_packet->Length=htons(8); // 数据长度
memcpy(lcp_packet->Data,&my_connection->LCP_magic_number,4);
// 再次发送 LCP包
int sent_count=send(session_socket,buff,ETH_HEARER_LEN_WITHOUT_CRC+PPPOE_HEADER_LEN+ntohs(buff->pppoe_length),0);
if(sent_count==-1) {
printf("error in sending echo reply! client %i\n%s\n",my_connection->index,strerror(errno));
exit(1);
}
}
if(lcp_packet->Code==0x01) {
lcp_packet->Code=0x02;
struct pppoe_packet cfg_req_ack;
memcpy(cfg_req_ack.eth_dst_mac,my_connection->peer_mac,MAC_LEN);
memcpy(cfg_req_ack.eth_src_mac,my_connection->my_mac,MAC_LEN);
cfg_req_ack.eth_type=htons(ETHER_TYPE_PPP_SESSION);
cfg_req_ack.pppoe_type=0x1;
cfg_req_ack.pppoe_ver=0x1;
cfg_req_ack.pppoe_code=0x00;
cfg_req_ack.pppoe_session_id=my_connection->pppoe_session_id;
cfg_req_ack.pppoe_length=htons(ntohs(lcp_packet->Length)+2);
struct ppp_frame_for_pppoe *ppp_frame_cfg_req_ack=(struct ppp_frame_for_pppoe *)cfg_req_ack.payload;
ppp_frame_cfg_req_ack->Protocol_Field=htons(LCP);
memcpy(ppp_frame_cfg_req_ack.Information,lcp_packet,ntohs(lcp_packet->Length));
//cfg_ack's ready!
int sent_count=send(session_socket,&cfg_req_ack,ETH_HEARER_LEN_WITHOUT_CRC+PPPOE_HEADER_LEN+ntohs(cfg_req_ack.pppoe_length),0);
if(sent_count==-1) {
printf("error in sending config ACK! client %i\n%s\n",my_connection->index,strerror(errno));
exit(1);
}
return 0;
}
return 1;
}
处理 session 阶段的 CHAP 阶段
- CHAP 是解密通信的过程
int CHAP_handle (struct Connection_info *my_connection,struct pppoe_packet *buff) {
struct ppp_frame_for_pppoe *ppp_frame=(struct ppp_frame_for_pppoe *)buff->payload;
struct LCP_packet *chap_packet=(struct LCP_packet *)ppp_frame->Information;
if(chap_packet->Code==3) {
//CHAP succeed!
return 1;
}
if(chap_packet->Code==4) {
printf("CHAP failed! client %i\n",my_connection->index);
exit(1);
}
if(chap_packet->Code==1) {
unsigned char string_to_be_digest[100];
memset(&string_to_be_digest,0,100);
memcpy(string_to_be_digest,&chap_packet->Identifier,sizeof(chap_packet->Identifier));
memcpy(string_to_be_digest+sizeof(chap_packet->Identifier),"aaaa",4);//CHAP secret!!!
struct CHAP_value *chap_value=(struct CHAP_value *)chap_packet->Data;
memcpy(string_to_be_digest+sizeof(chap_packet->Identifier)+4,chap_value->Value,chap_value->Value_size);
unsigned char md5[16];
memset(md5,0,16);
// 把当前自己的密码转换成 MD5 的值
struct MD5Context ctx;
MD5Init(&ctx); //初始
//更新
MD5Update(&ctx,string_to_be_digest,sizeof(chap_packet->Identifier)+4+chap_value->Value_size);
MD5Final(md5,&ctx); //计算
unsigned char temp[MAC_LEN];
memcpy(temp,buff->eth_dst_mac,MAC_LEN);
memcpy(buff->eth_dst_mac,buff->eth_src_mac,MAC_LEN);
memcpy(buff->eth_src_mac,temp,MAC_LEN);
buff->pppoe_length=htons(1+16+4+2+1+1+2);//caculate by myself!
chap_packet->Code=2;
chap_packet->Length=htons(1+1+2+1+16+4);//caculate by myself!
struct CHAP_value md5_value;
memset(&md5_value,0,sizeof(md5_value));
md5_value.Value_size=16;
memcpy(md5_value.Value,md5,16);
memcpy(md5_value.Name,"aaaa",4);//账号
// 把加密的MD5 存放到数据中 发送密码
memcpy(chap_packet->Data,&md5_value,sizeof(md5_value));
int send_count=send(session_socket,buff,ETH_HEARER_LEN_WITHOUT_CRC+PPPOE_HEADER_LEN+ntohs(buff->pppoe_length),0);
if (send_count==-1) {
printf("errno is sending the response of CHAP challenge! client %i\n%s\n",my_connection->index,strerror(errno));
exit(1);
}
}
}
获取输入参数的值
例:gcc -g -o hello ./hello.c
要获取如此 -g -o的值时使用 getopt()
getopt()
- 参数1 argc
- 参数2 argv
- 参数3 :const char * 读取参数的模板
- 返回值 int :字符ASCLL码
一次只会读取一个o 如果参数后面带值就加: 不带值就不加:
const char* options = "o:gc";
int option_character;
while ( (option_character=getopt(argc,argv,options))!=-1);
switch (option_character)
{
case 'o':
NIC=optarg;
break;
case 'c':
clients=atoi(optarg);
break;
default:
useage();
break;
}