AF_PACKET套接字上的Bind()最初失败...然后自行修复?

时间:2017-10-03 18:30:53

标签: c linux sockets

我无法解决这个问题。 当我运行我的代码时...我看到来自所有以太网类型和所有接口的数据,即使我成功绑定。 运行几分钟后......它自行修复。 然后我只能从特定的接口看到并且只有Ether类型匹配。 目标是遍历所有寻找特定MAC地址的接口。 当返回正确的响应时......我们会删除for循环,并根据需要配置所有内容。

// Copyright (c) 2017 Keith M. Bradley
// 
//
// History:
//    13 May 2017 Keith M. Bradley  Creation
//      all rights reserved.
//

/* ----------------------- Standard includes --------------------------------*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <pthread.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <net/if.h>
#include <net/ethernet.h>
#include <netpacket/packet.h>
#include <netdb.h>



#define     SIGNAL_THREAD_KILL  0xFF
#define     SIGNAL_THREAD_RESET 0xFE

// Ethernet II protocol to use (0x88b5 ... experimental #1).
#define     eType 0x88b5

#define msg_Hello "MikiePLC"
#define msg_Reply "IOM_1.0"
#define msg_Ack   "ackMikiePLC"


void* PLCThread(void* arg)
{
  // get our pointer to the PLC struct
  PLC   *myPLC = arg;
  // get and save our thread ID
  myPLC->tid = pthread_self();
  // thread index number?



  //------------------------------------------------------------------------------------------------------------------
  // locals
  uint8_t i;            // used as an index or loop counts.
  uint8_t j;            // used as 2nd index or loop counts.

  int     rtn;          // temp store or function return values.


  //------------------------------------------------------------------------------------------------------------------
  // create Ethernet buffers and variables.

  char*       outBuff = NULL;  // character buffer for sending out on Ethernet.  
  size_t      outBuffSz = 1540;

  char*       inBuff = NULL;   // character buffer for receiving in on Ethernet. 
  size_t      inBuffSz = 1540;

  int         fd;              // file descriptor for socket.

  int         flags;           // socket flags used bt fcntl().

  struct
  ifreq       ifr;             // used to get and set interface parameters.

  struct
  sockaddr_ll IOM_sa_flt;      // socket address struct, used to filter received Ethernet frames from the remote IO module ... used by bind().

  struct
  sockaddr_ll IOM_sa_rcv;      // socket address struct, used to store addr details of received frame ... used by recvfrom().
  socklen_t   IOM_sa_len;      // IOM_sa_rcv length.

  fd_set      myfds;           // used by select().

  struct
  timeval     rcv_tm_out;      // time out for select() to declare communications failed.



  //------------------------------------------------------------------------------------------------------------------
  // initialize Ethernet buffers and variables.

  // allocate memory for the Ethernet sending message buffer.
  outBuff = malloc(outBuffSz);
  if (outBuff == NULL)
    printf("\nNATIVE-PLCThread: Could not allocate outBuff memory.");
  memset(outBuff, '\0', outBuffSz);

  // allocate memory for the Ethernet recevied message buffer.
  inBuff = malloc(inBuffSz);
  if (inBuff == NULL)
    printf("\nNATIVE-PLCThread: Could not allocate inBuff memory.");

  // clear the sockaddr_ll structs.
  // (send was already cleared ... it is inside the PLC typdef).
  memset(&IOM_sa_rcv, 0, sizeof(IOM_sa_rcv));
  memset(&IOM_sa_flt, 0, sizeof(IOM_sa_flt));

  // set receiving sockaddr_ll struct size.
  IOM_sa_len = sizeof(IOM_sa_rcv);

  // setup the sending, receiving, and filtering sockaddr_ll's.
  myPLC->IOM_sa_snd.sll_family    = AF_PACKET;
  myPLC->IOM_sa_snd.sll_protocol  = htons(eType);

  IOM_sa_rcv.sll_family    = AF_PACKET;
  IOM_sa_rcv.sll_protocol  = htons(eType);

  IOM_sa_flt.sll_family    = AF_PACKET;
  IOM_sa_flt.sll_protocol  = htons(eType);



  //------------------------------------------------------------------------------------------------------------------
  // open our socket in dgram mode and setup the socket's features.
  fd = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_ALL));
  if (fd == -1)
  {
    fprintf(stderr, "%s\n", strerror(errno));
    printf("\nNATIVE-PLCThread: socket() failed !! - ");
  }

  // get the socket file descriptor flags.
  flags = fcntl(fd, F_GETFL, 0);

  // if succesful, set to non-blocking.
  if (flags != -1)
    fcntl(fd, F_SETFL, flags | O_NONBLOCK);




  if (fd != -1) // valid socket file descriptor means ok to proceed with IOM_Addr_search.
  {
    // IOM_MAC_search
    //   if MAC_Addr is configured, 
    //   loop to find which interface has the IOM (I/O Module).
    //
    // begin for loop ----------------------------------------------------------------------------------------------
    for (i = 1; 1; i++)
    {
      // we need to test for thread kill signal.
      if((myPLC->ThreadCtrl == SIGNAL_THREAD_KILL) || (myPLC->ThreadCtrl == SIGNAL_THREAD_RESET)) break;

      // if the user cleared the MAC addr while we were searching ... give up and run the engine. 
      if (myPLC->MAC_is_Valid != 0xa5) break;

      // clear the ifreq struct.
      memset(&ifr, 0, sizeof(ifr));

      // i is our 'for' loop counter and our current interface index.
      ifr.ifr_ifindex = i;

      // does the interface exist?
      if (ioctl(fd, SIOCGIFNAME, &ifr) == -1)
      {
        // if not, we ran past top of network interfaces.
        printf("\nNATIVE-PLCThread: IOM_MAC_search MAC address not found after searching all interfaces !!!\n");
        printf("\n_________________________________________________________________________________________\n");
        sleep(10);
        i = 0;
        continue;
      }
      // don't mess with loopback interface.
      if (strcmp(ifr.ifr_name,"lo") == 0) continue;

      // store the ifname using the pointer.
      strncpy (myPLC->ifName, ifr.ifr_name, sizeof(ifr.ifr_name) - 1);
      myPLC->ifName[IFNAMSIZ - 1] = '\0';

      // update the interface index in all sockaddr structs.
      myPLC->IOM_sa_snd.sll_ifindex = i;
      IOM_sa_rcv.sll_ifindex = i;
      IOM_sa_flt.sll_ifindex = i;

      // is the interface up?
      ioctl(fd, SIOCGIFFLAGS, &ifr);
      if ((ifr.ifr_flags & IFF_UP) == 0)
      {
        printf("\nNATIVE-PLCThread: IOM_Addr_search interface %s (index %d) is down.\n", myPLC->ifName, i);
        continue;
      }

      // bind it.
      if (bind(fd, (struct sockaddr*)&IOM_sa_flt, sizeof(IOM_sa_flt)) == -1)
      {
        fprintf(stderr, "%s\n", strerror(errno));
        printf("\nNATIVE-PLCThread: IOM_Addr_search bind() failed  !!!\n");
        continue;
      }

      // pause and flush? (didn't help at all)
      sleep(2);
      recvfrom(fd, inBuff, inBuffSz, 0, (struct sockaddr *)&IOM_sa_rcv, &IOM_sa_len);

      // fill outBuff with the hello message.
      strcpy(outBuff, msg_Hello);

      // send hello msg to the IOM with configured IOM_MAC_address.
      if (sendto(fd, outBuff, sizeof(msg_Hello), 0, (struct sockaddr *)&(myPLC->IOM_sa_snd), sizeof (myPLC->IOM_sa_snd)) == -1)
      {
        fprintf(stderr, "%s\n", strerror(errno));
        printf("\nNATIVE-PLCThread: IOM_Addr_search sendto() failed on interface %s (index %d) !!!\n", myPLC->ifName, i);
        continue;
      }

      // setup for the select() time out loop.
      rcv_tm_out.tv_sec = 0;
      rcv_tm_out.tv_usec = 50000;

      // begin while loop ------------------------------------------------------------------------------------------
      //
      // select() time out loop.
      //   wait for valid response from IOM_MAC_address (discard any ETHERNET 2 messages from other MAC's).
      //
      while ((rcv_tm_out.tv_sec != 0) || (rcv_tm_out.tv_usec != 0))
      {
        // create the file descriptor set for use by select().
        FD_ZERO(&myfds);
        FD_SET(fd, &myfds);

        // select() to sleep until received frame is ready, or the maximum length of time it would taked to get a response is exceeded.
        rtn = select(fd + 1, &myfds, NULL, NULL, &rcv_tm_out);
        if (rtn < 0)
        {
          fprintf(stderr, "%s\n", strerror(errno));
          printf("\nNATIVE-PLCThread: IOM_Addr_search select() returned <0 on interface %s (index %d).\n", myPLC->ifName, i);
          break;
        }
        // did we time out? ... then goto the next interface to search.
        else if (rtn == 0)
        {
          printf("\nNATIVE-PLCThread: IOM_Addr_search select() timed out (returned 0) on interface %s (index %d).\n", myPLC->ifName, i);
          break;
        }
        else  // select() returned > 0.
        {
          if (FD_ISSET(fd, &myfds))
          {
            // our socket is ready for reading ... 1st clear the buffer and the sock addr.
            memset(inBuff, '\0', inBuffSz);
            for (j = 0; j < 6; j++)
              IOM_sa_rcv.sll_addr[j] = 0;
            rtn = recvfrom(fd, inBuff, inBuffSz, 0, (struct sockaddr *)&IOM_sa_rcv, &IOM_sa_len);
            if(rtn < 0)
            {
              if (errno == EAGAIN)
                printf("\nNATIVE-PLCThread: IOM_Addr_search recvfrom() returned EAGAIN.\n");
              else if (errno == EWOULDBLOCK)
                printf("\nNATIVE-PLCThread: IOM_Addr_search recvfrom() returned EWOULDBLOCK.\n");
              else
              {
                fprintf(stderr, "%s\n", strerror(errno));
                printf("\nNATIVE-PLCThread: IOM_Addr_search recvfrom() returned unrecoverable error.\n");
              }
              break;
            }
            else if (rtn == 0)
              printf("\nNATIVE-PLCThread: IOM_Addr_search a_file_descriptor_is_set yet recvfrom() returned zero.\n");
            else  // recvfrom() returned > 0.
            {
              printf("\nNATIVE-PLCThread: IOM_Addr_search recvfrom() returned %d bytes on %s (index %d) MAC %02x:%02x:%02x:%02x:%02x:%02x   rcv_tm_out.tv_sec = %d.%d\n",
                                                                                                                                                rtn,
                                                                                                                                                myPLC->ifName,
                                                                                                                                                i,
                                                                                                                                                IOM_sa_rcv.sll_addr[0],
                                                                                                                                                IOM_sa_rcv.sll_addr[1],
                                                                                                                                                IOM_sa_rcv.sll_addr[2],
                                                                                                                                                IOM_sa_rcv.sll_addr[3],
                                                                                                                                                IOM_sa_rcv.sll_addr[4],
                                                                                                                                                IOM_sa_rcv.sll_addr[5],
                                                                                                                                                (int)rcv_tm_out.tv_sec,
                                                                                                                                                (int)rcv_tm_out.tv_usec);
              // check the IOM_sa_rcv.MAC_Addr ... is it who we want to talk to? ... if not discard.
              for (j = 0; j < 6; ++j)
                if ((myPLC->IOM_sa_snd.sll_addr[j]) == (IOM_sa_rcv.sll_addr[j])) continue;

              // MAC addr matches?
              if (j > 50) // set to 50 to debug ... should be 5.
              {
                printf("\nMAC Addr from our IOM.\n");
                // parse the received response to our hello msg.
                if (strcmp(inBuff, msg_Reply) == 0)
                {
                  // fill outBuff with the Ack message.
                  strcpy(outBuff, msg_Ack);
                  // send ack message to the IOM with configured IOM_MAC_address.
                  if (sendto(fd, outBuff, sizeof("ackMikiePLC"), 0, (struct sockaddr *)&(myPLC->IOM_sa_snd), sizeof (myPLC->IOM_sa_snd)) == -1)
                 {
                    fprintf(stderr, "%s\n", strerror(errno));
                    printf("\nNATIVE-PLCThread: IOM_Addr_search sendto() failed on interface %s (index %d) !!!\n", myPLC->ifName, i);
                    continue;
                  }
                  else
                  {
                    // declare ComStatus ok.
                    myPLC->ComStatus = 0xa5;
                    break;  // we have a winner !!!
                  }
                }
                else
                {
                  // declare ComStatus still NOT ok.
                  myPLC->ComStatus = 0x5a;
                  continue;
                }
              }
              else
              {
                printf("\nMAC Addr from a stranger (discarded)!!!\n");
                break;
              }
            }// END recvfrom() returned > 0.
          }// END if (FD_ISSET(fd, &myfds))
          else printf("\nNATIVE-PLCThread: IOM_Addr_search select() returned > 0 yet our only file descriptor was not set !!!\n");
        }// END select() returned > 0.
      }// END while loop -------------------------------------------------------------------------------------------
      if (myPLC->ComStatus == 0xa5) break;  // search is done ... break out of for loop.
    }// END for loop -----------------------------------------------------------------------------------------------
  }// END "valid socket fd means ok to proceed" ----------------------------------------------------------------------
  else printf("\nNATIVE-PLCThread: IOM_Addr_search socket() previously failed ... search cannot proceed.\n");





  // MAIN ENGINE LOOP !!!---------------------------------------------------------------------------------------------
  //
  // Loop for the life of this Sedona PLC object (unless Enable is false).
  //
  while((myPLC->ThreadCtrl != SIGNAL_THREAD_KILL) && (myPLC->ThreadCtrl != SIGNAL_THREAD_RESET))
  {

  }



  CleanExit: //--------------------------------------------------------------------------------------------------------

  close(fd);
  free(outBuff);
  free(inBuff);
  free(myPLC);
  pthread_exit(NULL);
}

下面是一个打印示例:

NATIVE-PLCThread:IOM_Addr_search recvfrom()在eth0上返回104个字节(索引2)MAC 00:1e:c9:7d:c4:36 rcv_tm_out.tv_sec = 0.49997

来自陌生人的MAC地址!!!

NATIVE-PLCThread:IOM_Addr_search recvfrom()在enp1s0上返回152个字节(索引3)MAC 00:1e:c9:7d:c4:36 rcv_tm_out.tv_sec = 0.49998

来自陌生人的MAC地址!!!

NATIVE-PLCThread:搜索所有接口后找不到IOM_MAC_search MAC地址!!!

我应该看到&#34; select()超时&#34;在eth0上,因为以太类型0x88b5没有任何响应。

1 个答案:

答案 0 :(得分:0)

我想我看到了问题。

我使用ETH_P_ALL创建了套接字。 我认为我可以在绑定中更具体,因为文档说我们可以。

到目前为止的初步测试并没有重现这个问题。

我看到很多消息来源说人们可以做我原来做的事情......所以这可能是Linux或驱动程序中的错误?