PPPOE 协议

PPPOE 协议是明文传输,抓捕是可以看到Data中的账号和密码的 是点对点 链路协议

  1. //PPPOE 是不绑定端口的而是绑定网卡
  2. // AF_PACKET链路层
  3. // ETHER_TYPE_DISCOVERY 协议码
  4. socket(AF_PACKET,SOCK_RAW,htons(ETHER_TYPE_DISCOVERY));

PPPOE 分为4阶段

1. discovery (发现)阶段

discovery 阶段

  1. 客户端广播一PADI(I = Initiation)请求,查找网络中的服务器 广播包
  2. 服务器端响应一PADO(O = Offer),将自己的一些信息告知客户端
  3. 客户端向此服务器发送一PADR(R = Request),请求会话号
  4. 服务器端响应一PADS(S = Session-confirmation),将分配的会话号告知对方 返回请求的会话号

    1. **至此,PPPoE会话建立。后续可以进行PPP的协商了**
  5. 绑定网卡

  6. 制定协议类型(没有现成的类型,协议自己根据动态包来自己写)

协议码 ETHER_TYPE_DISCOVERY 0x8863

  1. 发送PADI
  2. 收取PADO
  3. 发送PADR
  4. 收取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

  1. struct ifreq ifr;
  2. int fd = socket(AF_PACKET,SOCK_RAW,htons(ETHER_TYPE_DISCOVERY));
  3. // AF_PACKET 链路层协议
  4. ioctl(fd,SIOCGIFINDEX,&ifr);
  5. ifr.ifr_ifindex; //存放 网卡操作句柄
  6. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  7. // sockaddr_ll 链路层专用 类型
  8. srand((unsigned int)getpid()); //ready for rand
  9. unsigned short int count_client;
  10. struct Connection_info infos[clients];
  11. memset(infos,0,sizeof(infos));
  12. ifindex=get_ifindex(NIC);
  13. //创建discovery阶段的socket
  14. discovery_socket=socket(AF_PACKET,SOCK_RAW,htons(ETHER_TYPE_DISCOVERY));
  15. //创建session阶段的socket
  16. session_socket=socket(AF_PACKET,SOCK_RAW,htons(ETHER_TYPE_PPP_SESSION));
  17. if (discovery_socket == -1 || session_socket == -1) {
  18. printf("error in creating socket!\n%s\n",strerror(errno));
  19. exit(1);
  20. }
  21. // discovery 阶段的 socket
  22. struct sockaddr_ll sockaddr_used_to_bind_dis;
  23. memset(&sockaddr_used_to_bind_dis,0,sizeof(sockaddr_used_to_bind_dis));
  24. sockaddr_used_to_bind_dis.sll_protocol=ETHER_TYPE_DISCOVERY;//协议类型
  25. sockaddr_used_to_bind_dis.sll_ifindex=ifindex; // 网卡句柄
  26. if (bind(discovery_socket,(const struct sockaddr *)&sockaddr_used_to_bind_dis),sizeof(sockaddr_used_to_bind_dis)==-1) {
  27. printf("failed in binding discovery socket\n%s\n",strerror(errno));
  28. exit(1);
  29. }
  30. // session 阶段的 socket
  31. struct sockaddr_ll sockaddr_used_to_bind_ses;
  32. memset(&sockaddr_used_to_bind_ses,0,sizeof(sockaddr_used_to_bind_ses));
  33. sockaddr_used_to_bind_ses.sll_protocol=ETHER_TYPE_PPP_SESSION;//当前协议类型
  34. sockaddr_used_to_bind_ses.sll_ifindex=ifindex; // 网卡句柄
  35. if (bind(session_socket,(const struct sockaddr *)&sockaddr_used_to_bind_ses),sizeof(sockaddr_used_to_bind_ses)==-1) {
  36. printf("failed in binding session socket\n%s\n",strerror(errno));
  37. exit(1);
  38. }

例: 获取网卡 fd 固定写法

  1. int set_promisc (char *device_name) { //输入网卡名
  2. int sock=socket(AF_PACKET,SOCK_RAW,htons(ETHER_TYPE_DISCOVERY));
  3. struct ifreq ifr;
  4. memset(&ifr,0,sizeof(ifr));
  5. strncpy (ifr.ifr_name,device_name, sizeof(ifr.ifr_name) - 1);
  6. ifr.ifr_name[sizeof(ifr.ifr_name)-1]='\0';
  7. ioctl(sock,SIOCGIFFLAGS,&ifr);
  8. ifr.ifr_flags |= IFF_PROMISC;
  9. ioctl(sock,SIOCSIFFLAGS,&ifr);
  10. close(sock);
  11. return 0;
  12. }

制定协议类型

PADI / PADO / PADR / PADS类型结构体

是根据 抓到的包来定义结构体类型

  1. #define CODE_OF_PADI 0x09
  2. #define CODE_OF_PADO 0x07
  3. #define CODE_OF_PADR 0x19
  4. #define CODE_OF_PADS 0x65
  5. struct pppoe_tags{
  6. unsigned short tag_type; //数据类型
  7. unsigned short tag_len; //数据长度
  8. char tag_data[0]; //主机的唯一标识
  9. }
  10. 每个TAG都符合TLV结构,即Type-Length-Value,常见的有TAG有:
  11. Service-Name
  12. Type=0101,表示PPPoE服务的名字;服务器提供的服务名字必须要和Client相同。
  13. 但如果Service-NameLength0,就表示任何提供的PPPoE服务都可以接受
  14. // 单单不带 ppope头的 discorvey 结构体
  15. struct pppoe_discovery{
  16. unsigned char type:4;
  17. unsigned char version:4;
  18. unsigned char code; //协议类型
  19. unsigned short session; //
  20. unsigned short size; //长度
  21. struct pppoe_tags tag[0]; //主机的唯一标识
  22. }

struct pppoe_packet 总 discovery 协议类型

就是 一个 pppoe头 加上 PADI / PADO 类型结构体

  1. // 当前阶段 值
  2. #define CODE_OF_PADI 0x09 //第一个PADI是广播包
  3. #define CODE_OF_PADO 0x07
  4. #define CODE_OF_PADR 0x19
  5. #define CODE_OF_PADS 0x65
  6. struct pppoe_packet {
  7. // mac head
  8. char eth_dst_mac[6]; //目的MAK地址
  9. char eth_src_mac[6]; //源MAK地址
  10. unsigned short eth_type; //类型
  11. //pppoe head struct pppoe_discovery_PADI
  12. char pppoe_type:4;
  13. char pppoe_ver:4;
  14. unsigned char pppoe_code; // 协议类型
  15. unsigned short pppoe_session_id;// 网络 序号
  16. unsigned short pppoe_length; // 网络 序号 长度
  17. //pppoe data 最大的字符 - PPPOE的头长度 6
  18. char payload[MTU-PPPOE_HEADER_LEN];
  19. //char date[1]; // 一空间的指针 可以指向其他空间
  20. };

struct PPPOE_TAG PPPOE 的PADI阶段时的 PPPoE Tags 数据

  1. struct PPPOE_TAG {
  2. unsigned short TAG_TYPE; // TAG的类型
  3. unsigned short TAG_LENGTH;//TAG的长度
  4. unsigned char TAG_VALUE[MTU-PPPOE_HEADER_LEN-2];
  5. };

2. Session (会议) 阶段

PPPP 主头

  1. LCP协商 链路控制协议 此阶段主要是协商链路的一些参数 以及后续认证时使用的协议等
  2. 认证 此阶段服务器端将验证客户端的合法性 最常见的两种就是PAP和CHAP

    1. PAP认证:发送的认证信息是明文,可以通过抓包工具看到用户名、密码
    2. CHAP认证:发送的认证信息是密文,抓包工具无法解析出来真正的用户名、密码
  3. IPCP协商 此阶段进行IP、DNS、WINS等的协商
  4. 数据传输 上述任一阶段失败都会导致协议终止。 如果都成功,则可以开始进行IP层的通信了

Session 阶段和 discorvey 阶段的包是不一样的 要使用不同的结构体 和不同的socket

Session LCP 阶段的协议

LCP阶段发送的结构体和结构体中携带的数据结构体

  1. struct ppp_from_for_pppoe{
  2. unsigned short Protocol;
  3. char Information[1]; //当前结构体的有效数据
  4. }
  5. struct LCP_packet{
  6. unsigned char Code;
  7. unsigned char Identifier;
  8. unsigned short Length;
  9. char Date[1]; //当前结构体的有效数据
  10. // 或:char Date[最大传输单元 - PPPoE头长度 - 上面类型的大小];
  11. // 这样子写就不用自己动态去创建空间 就会自带空间
  12. }
  13. struct MRUBAK{
  14. unsigned char Type;
  15. unsigned char Length;
  16. unsigned short varu;
  17. }

认证阶段 传输账号名 密码(明文传输)

session阶段

  • 因为不知道 要进行多少次的 LCP CHAP IPCP 的通信协商 所以在同一个函数中判断
  1. void session (struct Connection_info *my_connection)
  2. { //判断 discovery 阶段完成 并且 创建一个子进程 去完成之后的 session阶段
  3. // 父进程 只去做 discovety阶段的创建
  4. if (my_connection->discovery_succeed && fork()==0)
  5. {
  6. // send_CFG_REQ() 发送 session 的第一个包(PPP LCP)
  7. unsigned char LCP_ID=send_CFG_REQ(my_connection);
  8. fd_set readable;
  9. FD_ZERO(&readable);
  10. FD_SET(session_sockeFt,&readable);
  11. while(1)
  12. {
  13. struct timeval tv;
  14. tv.tv_sec=TIME_OUT_SESSION;
  15. tv.tv_usec=0;
  16. int count;
  17. FD_ZERO(&readable);
  18. FD_SET(session_socket,&readable);
  19. count=select(session_socket+1,&readable,NULL,NULL,&tv);
  20. if(count==-1) {
  21. printf("error in selecting!\n%s\n",strerror(errno));
  22. exit(1);
  23. }
  24. if(count==0) {
  25. printf("no packet hits the session socket!\n");
  26. exit(1);
  27. }
  28. if(count>0)
  29. {
  30. struct pppoe_packet buff;
  31. memset(&buff,0,sizeof(buff));
  32. ssize_t recv_count;
  33. struct ppp_frame_for_pppoe *ppp_frame=(struct ppp_frame_for_pppoe *)buff.payload;
  34. recv_count=recv(session_socket,&buff,sizeof(buff),0);
  35. if(recv_count==-1)
  36. {
  37. printf("error in receiving packets from session socket!\n%s\n",strerror(errno));
  38. exit(1);
  39. }
  40. // 判断session_id 是否为本机发送的 session_id
  41. if(buff.pppoe_session_id!=my_connection->pppoe_session_id)
  42. {
  43. continue;
  44. }
  45. // 判断发送过来的包是否为 LCP协议
  46. if(ppp_frame->Protocol_Field==htons(LCP))
  47. {
  48. //处理LCP协议
  49. LCP_handle(my_connection,&buff,LCP_ID);
  50. }
  51. // 判断是否为 CHAP协议
  52. if(ppp_frame->Protocol_Field==htons(CHAP))
  53. { // 判断处理 CHAP协议是否成功
  54. if(CHAP_handle(my_connection,&buff)==1)
  55. { // 发送 IPCP
  56. send_IPCP_REQ(my_connection);
  57. }
  58. }
  59. // 判断是否为 IPCP协议
  60. if(ppp_frame->Protocol_Field==htons(IPCP))
  61. {
  62. IPCP_handle(my_connection,&buff);
  63. }
  64. }
  65. }
  66. }
  67. }

处理 session 阶段的 LCP 包

  1. // 处理 session 阶段的 LCP 包
  2. int LCP_handle (struct Connection_info *my_connection,struct pppoe_packet *buff,unsigned char LCP_ID)
  3. {
  4. struct ppp_frame_for_pppoe *ppp_frame=(struct ppp_frame_for_pppoe *)buff->payload;
  5. struct LCP_packet *lcp_packet=(struct LCP_packet *)ppp_frame->Information;
  6. if(lcp_packet->Code==0x02 && lcp_packet->Identifier==LCP_ID) {
  7. //we got a configure ACK!
  8. return 0;
  9. }
  10. // 解析
  11. if(lcp_packet->Code==0x09) {
  12. lcp_packet->Code=0x0a;
  13. unsigned char temp[MAC_LEN];
  14. // 拷贝源MAC地址和目的MAC地址
  15. memcpy(temp,buff->eth_src_mac,MAC_LEN);
  16. memcpy(buff->eth_src_mac,buff->eth_dst_mac,MAC_LEN);
  17. memcpy(buff->eth_dst_mac,temp,MAC_LEN);
  18. buff->pppoe_length=htons(10); // 修改 协议类型
  19. lcp_packet->Length=htons(8); // 数据长度
  20. memcpy(lcp_packet->Data,&my_connection->LCP_magic_number,4);
  21. // 再次发送 LCP包
  22. int sent_count=send(session_socket,buff,ETH_HEARER_LEN_WITHOUT_CRC+PPPOE_HEADER_LEN+ntohs(buff->pppoe_length),0);
  23. if(sent_count==-1) {
  24. printf("error in sending echo reply! client %i\n%s\n",my_connection->index,strerror(errno));
  25. exit(1);
  26. }
  27. }
  28. if(lcp_packet->Code==0x01) {
  29. lcp_packet->Code=0x02;
  30. struct pppoe_packet cfg_req_ack;
  31. memcpy(cfg_req_ack.eth_dst_mac,my_connection->peer_mac,MAC_LEN);
  32. memcpy(cfg_req_ack.eth_src_mac,my_connection->my_mac,MAC_LEN);
  33. cfg_req_ack.eth_type=htons(ETHER_TYPE_PPP_SESSION);
  34. cfg_req_ack.pppoe_type=0x1;
  35. cfg_req_ack.pppoe_ver=0x1;
  36. cfg_req_ack.pppoe_code=0x00;
  37. cfg_req_ack.pppoe_session_id=my_connection->pppoe_session_id;
  38. cfg_req_ack.pppoe_length=htons(ntohs(lcp_packet->Length)+2);
  39. struct ppp_frame_for_pppoe *ppp_frame_cfg_req_ack=(struct ppp_frame_for_pppoe *)cfg_req_ack.payload;
  40. ppp_frame_cfg_req_ack->Protocol_Field=htons(LCP);
  41. memcpy(ppp_frame_cfg_req_ack.Information,lcp_packet,ntohs(lcp_packet->Length));
  42. //cfg_ack's ready!
  43. int sent_count=send(session_socket,&cfg_req_ack,ETH_HEARER_LEN_WITHOUT_CRC+PPPOE_HEADER_LEN+ntohs(cfg_req_ack.pppoe_length),0);
  44. if(sent_count==-1) {
  45. printf("error in sending config ACK! client %i\n%s\n",my_connection->index,strerror(errno));
  46. exit(1);
  47. }
  48. return 0;
  49. }
  50. return 1;
  51. }

处理 session 阶段的 CHAP 阶段

  • CHAP 是解密通信的过程
  1. int CHAP_handle (struct Connection_info *my_connection,struct pppoe_packet *buff) {
  2. struct ppp_frame_for_pppoe *ppp_frame=(struct ppp_frame_for_pppoe *)buff->payload;
  3. struct LCP_packet *chap_packet=(struct LCP_packet *)ppp_frame->Information;
  4. if(chap_packet->Code==3) {
  5. //CHAP succeed!
  6. return 1;
  7. }
  8. if(chap_packet->Code==4) {
  9. printf("CHAP failed! client %i\n",my_connection->index);
  10. exit(1);
  11. }
  12. if(chap_packet->Code==1) {
  13. unsigned char string_to_be_digest[100];
  14. memset(&string_to_be_digest,0,100);
  15. memcpy(string_to_be_digest,&chap_packet->Identifier,sizeof(chap_packet->Identifier));
  16. memcpy(string_to_be_digest+sizeof(chap_packet->Identifier),"aaaa",4);//CHAP secret!!!
  17. struct CHAP_value *chap_value=(struct CHAP_value *)chap_packet->Data;
  18. memcpy(string_to_be_digest+sizeof(chap_packet->Identifier)+4,chap_value->Value,chap_value->Value_size);
  19. unsigned char md5[16];
  20. memset(md5,0,16);
  21. // 把当前自己的密码转换成 MD5 的值
  22. struct MD5Context ctx;
  23. MD5Init(&ctx); //初始
  24. //更新
  25. MD5Update(&ctx,string_to_be_digest,sizeof(chap_packet->Identifier)+4+chap_value->Value_size);
  26. MD5Final(md5,&ctx); //计算
  27. unsigned char temp[MAC_LEN];
  28. memcpy(temp,buff->eth_dst_mac,MAC_LEN);
  29. memcpy(buff->eth_dst_mac,buff->eth_src_mac,MAC_LEN);
  30. memcpy(buff->eth_src_mac,temp,MAC_LEN);
  31. buff->pppoe_length=htons(1+16+4+2+1+1+2);//caculate by myself!
  32. chap_packet->Code=2;
  33. chap_packet->Length=htons(1+1+2+1+16+4);//caculate by myself!
  34. struct CHAP_value md5_value;
  35. memset(&md5_value,0,sizeof(md5_value));
  36. md5_value.Value_size=16;
  37. memcpy(md5_value.Value,md5,16);
  38. memcpy(md5_value.Name,"aaaa",4);//账号
  39. // 把加密的MD5 存放到数据中 发送密码
  40. memcpy(chap_packet->Data,&md5_value,sizeof(md5_value));
  41. int send_count=send(session_socket,buff,ETH_HEARER_LEN_WITHOUT_CRC+PPPOE_HEADER_LEN+ntohs(buff->pppoe_length),0);
  42. if (send_count==-1) {
  43. printf("errno is sending the response of CHAP challenge! client %i\n%s\n",my_connection->index,strerror(errno));
  44. exit(1);
  45. }
  46. }
  47. }

获取输入参数的值

例:gcc -g -o hello ./hello.c

要获取如此 -g -o的值时使用 getopt()

getopt()

  • 参数1 argc
  • 参数2 argv
  • 参数3 :const char * 读取参数的模板
  • 返回值 int :字符ASCLL码

一次只会读取一个o 如果参数后面带值就加: 不带值就不加:

  1. const char* options = "o:gc";
  2. int option_character;
  3. while ( (option_character=getopt(argc,argv,options))!=-1);
  4. switch (option_character)
  5. {
  6. case 'o':
  7. NIC=optarg;
  8. break;
  9. case 'c':
  10. clients=atoi(optarg);
  11. break;
  12. default:
  13. useage();
  14. break;
  15. }