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 randunsigned short int count_client;struct Connection_info infos[clients];memset(infos,0,sizeof(infos));ifindex=get_ifindex(NIC);//创建discovery阶段的socketdiscovery_socket=socket(AF_PACKET,SOCK_RAW,htons(ETHER_TYPE_DISCOVERY));//创建session阶段的socketsession_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 阶段的 socketstruct 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 阶段的 socketstruct 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 0x65struct pppoe_tags{unsigned short tag_type; //数据类型unsigned short tag_len; //数据长度char tag_data[0]; //主机的唯一标识}每个TAG都符合TLV结构,即Type-Length-Value,常见的有TAG有:Service-NameType=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 0x65struct pppoe_packet {// mac headchar eth_dst_mac[6]; //目的MAK地址char eth_src_mac[6]; //源MAK地址unsigned short eth_type; //类型//pppoe head struct pppoe_discovery_PADIchar pppoe_type:4;char pppoe_ver:4;unsigned char pppoe_code; // 协议类型unsigned short pppoe_session_id;// 网络 序号unsigned short pppoe_length; // 网络 序号 长度//pppoe data 最大的字符 - PPPOE的头长度 6char 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_idif(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){ // 发送 IPCPsend_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;}
