OpenLLDP 源码分析

项目简介

  • LLDP协议由802.1ab所定义。它是一个二层协议,一般称之为数据链路发现协议。这里对于该协议的实现原理不做详细介绍。具体原理可以参见IEEE 802.1ab文档连接

  • OpenLLDP为802.1ab的开源实现,号称支Linux,macOS,FreeBSD,NetBSD等众多类unix系统。参见维基百科对于openlldp的介绍

  • 下面为OpenLLDP的
    项目主页
    sourceforge页面

  • 这里要提前指出的是,OpenLLDP的实现的并非非常标准的lldp。可以说它只是简单的实现了最基本的lldp功能。若需要lldp功能更多的实现,恐怕还要自己进行功能的添加了。

  • LLDP协议可以参考这篇文章

代码总览

关键数据结构

接口管理

每个网络接口都对应了这样一个数据结构,它用来存储网络接口的接口名,接口索引,mac地址,创建的套接字描述符,和邻居信息指针(这里问lldp_msap)以及最为重要的接收和发送端口缓存(具体为lldp_rx_port结构体和lldp_tx_port结构体)等等。

struct lldp_port {
  struct lldp_port *next;     //用于将接口串为链
  int socket;                       // 该接口的套接字.
  char *if_name;                 // 接口名.
  uint32_t if_index;            //接口索引 .
  uint32_t mtu;                  // 接口 MTU.
  uint8_t source_mac[6];    //接口mac
  uint8_t source_ipaddr[4];//接口IP
  struct lldp_rx_port rx;     //消息接收状态机,接收缓存
  struct lldp_tx_port tx;     //消息发送状态机,发送缓存
  uint8_t portEnabled;       //端口使能
  uint8_t adminStatus;      //端口状态
  
  /* I'm not sure where this goes... the state machine indicates it's per-port */
  uint8_t rxChanges;
  
  // I'm really unsure about the best way to handle this... 
  uint8_t tick;
  time_t last_tick;
 
  struct lldp_msap *msap_cache;
  // 802.1AB Appendix G flag variables.
  uint8_t  auto_neg_status;
  uint16_t auto_neg_advertized_capabilities;
  uint16_t operational_mau_type;
};

LLDP发送状态机

如下所示,该结构体为存储发送缓存以及发送端口状态机相关参数。state在收发报文时根据lldp协议改变状态,从而影响了发送状态机的运转轨,somethingChangedLocal在本端信息改变时被置位。它被置一标志着lldp开启快速发送机制,会将本端信息快速的传递给直连邻居。

struct lldp_tx_port {
    uint8_t *frame;       /*frame为缓存发送报文的指针 */
    uint64_t sendsize; /*待发送的缓存字节数 */
    uint8_t state;        /*发送状态 */
    uint8_t somethingChangedLocal; /*标志本地mib改变*/
    uint16_t txTTL;                /*< IEEE 802.1AB var (from where?) */
    struct lldp_tx_port_timers timers; /*发送记时*/
    struct lldp_tx_port_statistics statistics; /**< The lldp tx statistics for this interface */
};

LLDP接收状态机

和tx类似,用于处理接收报文,当收到邻居通告的LLDP后somethingChangedRemote被置1

    struct lldp_rx_port {     
        uint8_t *frame;  
        ssize_t recvsize;  
        uint8_t state;  
        uint8_t badFrame;  
        uint8_t rcvFrame;  
        uint8_t rxInfoAge;  
        uint8_t somethingChangedRemote;  
        uint8_t tooManyNeighbors;  
        struct lldp_rx_port_timers timers;  
        struct lldp_rx_port_statistics statistics;  
    };  

邻居MIB管理

这个数据结构主要是存储一条邻居信息。多个邻居信息,则以链表的形式组织在lldp_port结构体中。id为lldpdu中必填的tlv字段的chassis subtype id和 port subtype id组成。基本上不同的邻居信息会有不同的lldp_msap id。length为经过格式化之后的全部lldp报文tlv长度。rxInfoTTL则为老化时间。tlv_list为存储tlv的链表,每个节点是一个tlv。该tlv经过了“格式化”而非lldpdu中的tlv。只有经过了什么样的格式化,后续会讲述。

    struct lldp_msap {  
      struct lldp_msap *next;   
      uint8_t *id;                     
      uint8_t length;             
      struct lldp_tlv_list *tlv_list;  
        
      // XXX Revisit this  
      // A pointer to the TTL TLV  
      // This is a hack to decrement  
      // the timer properly for   
      // lldpneighbors output  
      struct lldp_tlv *ttl_tlv;  
      
      /* IEEE 802.1AB MSAP-specific counters */  
      uint16_t rxInfoTTL;  
    };  

tlv为一条lldp的tlv,多个tlv被组织为链表的形式。

struct lldp_tlv_list {  
    struct lldp_tlv_list *next;  
    struct lldp_tlv *tlv;  
}; 

tlv的数据结构定义,严格的T(type) L(length)V(value)形式。

struct lldp_tlv {  
        uint8_t  type;  
        uint16_t length;  
        uint8_t  *info_string;  
    };  

结构说明

整体结构

如图1所示

  • openlldp整体上是对每个接口独立管理
  • 每个接口将保存本地MIB(local MIB),和远程MIB(remote MIB)以及该接口的信息(port info).MIB即是存放LLDP协议获取的信息.
  • 此外该接口还有一个发送管理(tx manger)和接收管理(rx manager).
    发送管理与本地MIB关联,当本地MIB变化时负责进行通告。接收管理负责接收相邻设备MIB变化时发送来的LLDP信息,存放于远程MIB中。
图1 整体结构图

详细结构

如图2所示,具体来组织方式是4层链表嵌套.

  • 第一层是lldp_port, 是将各个接口的管理结构链式管理.
  • 第二层是lldp_masp , 其存放的是各个接口相邻设备的remote MIB。
  • 第三层是lldp_tlv_list, 每一个管理一个相邻设备的tlv。
图2 数据存放链表

具体代码分析

了解了上面openlldp的原理可以知道重要的代码集中在以下几处

  • 原始套接字封装
  • 报文接收处理模块
  • 报文发送处理模块
    下面就针对上面几点,进行分别说明.注意:...表示省略不重要代码

原始套接字

接口初始化

主要是建立原始套接字,设置广播,初始化缓冲区.

int socketInitializeLLDP(struct lldp_port *lldp_port)
{
  struct ifreq *ifr = calloc(1, sizeof(struct ifreq));
  struct sockaddr_ll *sll = calloc(1, sizeof(struct sockaddr_ll));
    int retval             = 0;

    ... 

    /*  创建原始套接字,选择协议号0x88cc*/  
    sll->sll_family = PF_PACKET;
    sll->sll_ifindex = lldp_port->if_index;
    sll->sll_protocol = htons(0x88CC);
    retval = bind(lldp_port->socket, (struct sockaddr *)sll, sizeof(struct sockaddr_ll));

    if(retval < 0) {
        debug_printf(DEBUG_NORMAL, "Error binding raw socket to interface %s in %s():%d!\n", lldp_port->if_name, __FUNCTION__, __LINE__);
        return XENOSOCK;
    }

    ifr->ifr_ifindex = lldp_port->if_index;

    strncpy(ifr->ifr_name, &lldp_port->if_name[0], strlen(lldp_port->if_name));

    if(strlen(ifr->ifr_name) == 0) {
        debug_printf(DEBUG_NORMAL, "Invalid interface name in %s():%d\n", __FUNCTION__, __LINE__);
        return XENOSOCK;
    }

    if(retval < 0) {
        debug_printf(DEBUG_NORMAL, "Error getting hardware (MAC) address for interface '%s' in %s():%d - %d:%s!\n", lldp_port->if_name, __FUNCTION__, __LINE__, errno, strerror(errno));

        return retval;
    }

    retval = _getip(lldp_port);

    if (retval < 0) {
        debug_printf(DEBUG_NORMAL, "Error getting interface IP address for interface '%s' in %s():%d!\n", lldp_port->if_name, __FUNCTION__, __LINE__);   
    }*/

    refreshInterfaceData(lldp_port);

    retval = ioctl(lldp_port->socket, SIOCGIFFLAGS, ifr);

    if (retval == -1)
    {
        debug_printf(DEBUG_NORMAL, "Can't get flags for interface '%s' in %s():%d!\n", lldp_port->if_name, __FUNCTION__, __LINE__);
    }

    //检查接口是否UP
    if ((ifr->ifr_flags & IFF_UP) == 0) {

        debug_printf(DEBUG_INT, "Interface '%s' is down. portEnabled = 0.\n", lldp_port->if_name);  

        lldp_port->portEnabled = 0;
    } 

    // set allmulti on interface
    // need to devise a graceful way to turn off allmulti otherwise it is left on for the interface when problem is terminated.
    retval = ioctl(lldp_port->socket, SIOCGIFFLAGS, ifr);

   ... 

  //由于lldp交互的数据报文为多播报文,故此这里要设置端口接收并处理多播报 文。若不这么设置,端口是接收不到多播报文的.

    ifr->ifr_flags |=  IFF_ALLMULTI; // allmulti on (verified via ifconfig)
    //  ifr.ifr_flags &= ~IFF_ALLMULTI; // allmulti off (I think)

    retval = ioctl(lldp_port->socket, SIOCSIFFLAGS, ifr);

    if (retval == -1)
    {
        debug_printf(DEBUG_NORMAL, "Can't set flags for interface '%s' in %s():%d!\n", lldp_port->if_name, __FUNCTION__, __LINE__);
    }

    // Discover MTU of our interface.
    retval = ioctl(lldp_port->socket, SIOCGIFMTU, ifr);

    if(retval < 0) 
    {
        debug_printf(DEBUG_NORMAL, "Can't determine MTU for interface '%s' in %s():%d!\n", lldp_port->if_name, __FUNCTION__, __LINE__);

        return retval;
    }   

    lldp_port->mtu = ifr->ifr_ifru.ifru_mtu;

    debug_printf(DEBUG_INT, "[%s] MTU is %d\n", lldp_port->if_name, lldp_port->mtu);

//建立,发送以及接收缓冲区
    lldp_port->rx.frame = calloc(1, lldp_port->mtu - 4);
    lldp_port->tx.frame = calloc(1, (lldp_port->mtu - 2));
    
    if(!lldp_port->rx.frame) {
        debug_printf(DEBUG_NORMAL, "[ERROR] Unable to malloc buffer in %s() at line: %d!\n", __FUNCTION__, __LINE__);
    } else {
        debug_printf(DEBUG_INT, "Created framebuffer for %s at %x\n", lldp_port->if_name, &lldp_port->rx.frame);
    }

    if(!lldp_port->tx.frame) {
        debug_printf(DEBUG_NORMAL, "[ERROR] Unable to malloc buffer in %s() at line: %d!\n", __FUNCTION__, __LINE__);
    } else {
        debug_printf(DEBUG_INT, "Created framebuffer for %s at %x\n", lldp_port->if_name, &lldp_port->tx.frame);
    }

    debug_printf(DEBUG_INT, "Interface (%s) MTU is %d.\n", lldp_port->if_name, lldp_port->mtu);

    free(ifr);
    free(sll);

    return 0;
}

发数据

写数据比较简单,直接向原始套接字发送数据即可

ssize_t lldp_write(struct lldp_port *lldp_port) {

  // Write the frame to the wire.
  return write(lldp_port->socket, lldp_port->tx.frame, lldp_port->tx.sendsize);                                                                              
}

收数据

也是直接从原始套接字中获取数据
ssize_t lldp_read(struct lldp_port *lldp_port) {
  // allocate the bpf_buf to recieve as many packets as will fit in lldp_port->mtu
  // which is the bpf internal buffer size.
  struct bpf_hdr *bpf_buf = malloc(lldp_port->mtu);

  lldp_port->rx.recvsize = read(lldp_port->socket, bpf_buf, lldp_port->mtu);
  
  // Allocate the buffer to be the length of the captured packet
  uint8_t *frame_buffer = malloc(bpf_buf->bh_caplen);

  //XXX:  BUG HERE - We could actually have more than one packet in bpf_buf
  //      we should process bpf_buf in a loop until we have processed all
  //      of the packets in the buffer.  This would mean changing lldp_port->rx
  //      so that there was a linked list of packets in frame so that the next
  //      code section could process all the packets in the queue.  
  //
  //      However the chance of more than one packet being in the buffer is low
  //      and we can safely drop any other frames as well. 
  if(frame_buffer) {
    debug_printf(DEBUG_INT, "(%s) Raw BPF Frame with BPF header: \n", lldp_port->if_name);
    debug_printf(DEBUG_INT, "BPF Header Length: %d\n", bpf_buf->bh_hdrlen);
    debug_hex_dump(DEBUG_INT, (uint8_t *)bpf_buf, lldp_port->rx.recvsize);
  
    // Copy the captured data to the buffer, NOTE this may not be the whole packet!!!
    memcpy(frame_buffer, ((char*) bpf_buf + bpf_buf->bh_hdrlen), bpf_buf->bh_caplen);
    
    debug_printf(DEBUG_INT, "(%s) Raw BPF Frame without BPF header: \n", lldp_port->if_name);
    debug_hex_dump(DEBUG_INT, (uint8_t *)frame_buffer, bpf_buf->bh_caplen);
    // Correct the rx.recvsize to reflect the lenght of the packet without the bpf_hdr
    lldp_port->rx.recvsize = bpf_buf->bh_caplen;
    
    // Free the tmp buffer 
    free(bpf_buf);

    // Now free the old buffer
    free(lldp_port->rx.frame);
    
    // Now assign the new buffer
    lldp_port->rx.frame = frame_buffer;
  } else {
    debug_printf(DEBUG_NORMAL, "Couldn't malloc!  Skipping frame to prevent leak...\n");
  }

  return(lldp_port->rx.recvsize);
}

LLDP协议处理

LLDP接收处理

rxProcessFrame主要是

  • 提取tlv检查合法性,解析为对应的lldp_tlv节点,并缓存相应的信息,最终构造lldp_msap结构体。并更新邻居信息。
  • 在update_msap_cache函数中,会判断rxProcessFrame函数构造的lldp_msap在本端口的lldp_msap链表中是否存在。若存在,那么直接进行替换(不检查是否完全完全相等,简便的做法)。若不存在,则说明是一个新邻居,那么完成邻居信息结构体lldp_msap的链表插入工作。
  • 在lldp报文中tlv被组织为7bit的type字段,9bit的length字段。这种组织方式,在存储tlv时,极为不便。这里将这种组织方式转化为lldp_tlv的组织方式。type和length都可以使用现有的数据类型表示,方便程序的编写。
    int rxProcessFrame(struct lldp_port *lldp_port) {  
    …  
    …  
      
    /* 
    主要是验证报文的正确性:具体要验证报文的目的地址以及报文类型字段 
    */  
    /* 确定是LLDP */  
        expect_hdr.dst[0] = 0x01;  
        expect_hdr.dst[1] = 0x80;  
        expect_hdr.dst[2] = 0xc2;  
        expect_hdr.dst[3] = 0x00;  
        expect_hdr.dst[4] = 0x00;  
        expect_hdr.dst[5] = 0x0e;  
        expect_hdr.ethertype = htons(0x88cc);  
      
  /*指向接收缓冲区*/
        ether_hdr = (struct eth_hdr *)&lldp_port->rx.frame[0];  
      
        debug_printf(DEBUG_INT, "LLPDU Dst: ");  
        debug_hex_printf(DEBUG_INT, (uint8_t *)ether_hdr->dst, 6);  
      
        debug_printf(DEBUG_EXCESSIVE, "Expect Dst: ");  
        debug_hex_printf(DEBUG_EXCESSIVE, (uint8_t *)expect_hdr.dst, 6);  
      
        /* Validate the frame's destination */  
        if(  
                ether_hdr->dst[0] != expect_hdr.dst[0] ||  
                ether_hdr->dst[1] != expect_hdr.dst[1] ||  
                ether_hdr->dst[2] != expect_hdr.dst[2] ||  
                ether_hdr->dst[3] != expect_hdr.dst[3] ||  
                ether_hdr->dst[4] != expect_hdr.dst[4] ||  
                ether_hdr->dst[5] != expect_hdr.dst[5] ) {  
      
            debug_printf(DEBUG_NORMAL, "[ERROR] This frame is incorrectly addressed to: ");  
            debug_hex_printf(DEBUG_NORMAL, (uint8_t *)ether_hdr->dst, 6);  
            debug_printf(DEBUG_NORMAL, "[ERROR] This frame should be addressed to: ");  
            debug_hex_printf(DEBUG_NORMAL, (uint8_t *)expect_hdr.dst, 6);  
            debug_printf(DEBUG_NORMAL, "[ERROR] statsFramesInTotal will *NOT* be incremented\n");  
      
            badFrame++;  
        }  
      
        debug_printf(DEBUG_INT, "LLPDU Src: ");  
        debug_hex_printf(DEBUG_INT, (uint8_t *)ether_hdr->src, 6);  
      
        debug_printf(DEBUG_INT, "LLPDU Ethertype: %x\n", htons(ether_hdr->ethertype));  
      
        debug_printf(DEBUG_EXCESSIVE, "Expect Ethertype: %x\n", htons(expect_hdr.ethertype));  
      
        /* Validate the frame's ethertype */  
        if(ether_hdr->ethertype != expect_hdr.ethertype) {  
            debug_printf(DEBUG_NORMAL, "[ERROR] This frame has an incorrect ethertype of: '%x'.\n", htons(ether_hdr->ethertype));  
      
            badFrame++;  
        }  
      
        if(!badFrame) {  
            lldp_port->rx.statistics.statsFramesInTotal ++;  
        }  
      
    …  
    …  
      
    /* 
    请注意lldp报文TLV的格式,前7个bits为tlv类型字段,后9个为数据长度字段。 
    */  
    /* Grab the first 9 bits */  
            tlv_length = htons(*tlv_hdr) & 0x01FF;  
      
            /* Then shift to get the last 7 bits */  
            tlv_type = htons(*tlv_hdr) >> 9;  
  
    /* 
    lldp报文中tlv最少为4个,分别为Chasis ID TLV、Port ID TLV、TTL TLV、End TLV 
    */  
            /* Validate as per 802.1AB section 10.3.2*/  
            if(num_tlvs <= 3) {  
                if(num_tlvs != tlv_type) {  
                    debug_printf(DEBUG_NORMAL, "[ERROR] TLV number %d should have tlv_type %d, but is actually %d\n", num_tlvs, num_tlvs, tlv_type);  
                    debug_printf(DEBUG_NORMAL, "[ERROR] statsFramesDiscardedTotal and statsFramesInErrorsTotal will be incremented as per 802.1AB 10.3.2\n");  
                    lldp_port->rx.statistics.statsFramesDiscardedTotal++;  
                    lldp_port->rx.statistics.statsFramesInErrorsTotal++;  
                    badFrame++;  
                }  
            }  
    /* 
    缓存lldp报文中tlv的值 
    */  
    tlv->type        = tlv_type;  
    tlv->length      = tlv_length;  
        if(tlv->length > 0)       
          tlv->info_string = calloc(1, tlv_length);  
    /* 
    如果LLDP中的tlv为TTL,那么则更新rx.timers.rxTTL的值 
    */  
      if(tlv_type == TIME_TO_LIVE_TLV) {  
          if(tlv_length != 2) {  
                    debug_printf(DEBUG_NORMAL, "[ERROR] TTL TLV has an invalid length!  Should be '2', but is '%d'\n", tlv_length);  
    #ifndef WIN32  
    #warning We should actually discard this frame and print out an error...  
    #warning Write a unit test to stress this  
    #endif // WIN32  
                } else {  
                    lldp_port->rx.timers.rxTTL = htons(*(uint16_t *)&tlv_info_string[0]);  
            msap_ttl_tlv = tlv;  
                    debug_printf(DEBUG_EXCESSIVE, "rxTTL is: %d\n", lldp_port->rx.timers.rxTTL);  
                }  
            }  
      
            if(tlv->info_string) {  
                memset(tlv->info_string, 0x0, tlv_length);  
                memcpy(tlv->info_string, tlv_info_string, tlv_length);  
            }   
      
            /* Validate the TLV */  
            if(validate_tlv[tlv_type] != NULL) {  
                debug_printf(DEBUG_EXCESSIVE, "Found a validator for TLV type %d.\n", tlv_type);  
      
                debug_hex_dump(DEBUG_EXCESSIVE, tlv->info_string, tlv->length);  
      
                if(validate_tlv[tlv_type](tlv) != XVALIDTLV) {  
                    badFrame++;  
                }  
            } else {  
          // NOTE: Any organizationally specific TLVs should get processed through validate_generic_tlv  
                debug_printf(DEBUG_EXCESSIVE, "Didn't find specific validator for TLV type %d.  Using validate_generic_tlv.\n", tlv_type);  
                if(validate_generic_tlv(tlv) != XVALIDTLV) {  
                    badFrame++;  
                }  
            }  
    …  
    …  
    /* 
    将之前缓存的lldp报文中tlv加入到tlv_list中 
    */  
        cached_tlv = initialize_tlv();  
      
        if(tlvcpy(cached_tlv, tlv) != 0) {  
          debug_printf(DEBUG_TLV, "Error copying TLV for MSAP cache!\n");  
          }   
      
        debug_printf(DEBUG_EXCESSIVE, "Adding exploded TLV to MSAP TLV list.\n");  
        // Now we can start stuffing the msap data... ;)  
        add_tlv(cached_tlv, &tlv_list);  
    /* 
     如果是CHASSIS_ID_TLV和PORT_ID_TLV,那么则缓存它们的值。并将它们拼接为msap_id 
    */  
        if(tlv_type == CHASSIS_ID_TLV) {  
          debug_printf(DEBUG_NORMAL, "Copying TLV1 for MSAP Processing...\n");  
          msap_tlv1 = initialize_tlv();  
          tlvcpy(msap_tlv1, tlv);  
        } else if(tlv_type == PORT_ID_TLV) {  
          debug_printf(DEBUG_NORMAL, "Copying TLV2 for MSAP Processing...\n");  
          msap_tlv2 = initialize_tlv();  
          tlvcpy(msap_tlv2, tlv);  
      
            
          //Minus 2, for the chassis id subtype and port id subtype...   
          // IEEE 802.1AB specifies that the MSAP shall be composed of   
          // The value of the subtypes.   
          msap_id = calloc(1, msap_tlv1->length - 1  + msap_tlv2->length - 1);  
      
          if(msap_id == NULL)  
            {  
              debug_printf(DEBUG_NORMAL, "[ERROR] Unable to malloc buffer in %s() at line: %d!\n", __FUNCTION__, __LINE__);  
            }  
            
          // Copy the first part of the MSAP   
          memcpy(msap_id, &msap_tlv1->info_string[1], msap_tlv1->length - 1);  
            
          // Copy the second part of the MSAP   
          memcpy(&msap_id[msap_tlv1->length - 1], &msap_tlv2->info_string[1], msap_tlv2->length - 1);  
            
          msap_length = (msap_tlv1->length - 1) + (msap_tlv2->length - 1);  
            
          debug_printf(DEBUG_MSAP, "MSAP TLV1 Length: %d\n", msap_tlv1->length);  
          debug_printf(DEBUG_MSAP, "MSAP TLV2 Length: %d\n", msap_tlv2->length);  
            
          debug_printf(DEBUG_MSAP, "MSAP is %d bytes: ", msap_length);  
          debug_hex_printf(DEBUG_MSAP, msap_id, msap_length);  
          debug_hex_dump(DEBUG_MSAP, msap_id, msap_length);  
      
          // Free the MSAP pieces  
          destroy_tlv(&msap_tlv1);  
          destroy_tlv(&msap_tlv2);  
            
          msap_tlv1 = NULL;  
          msap_tlv2 = NULL;  
                
    /* 指示有新的邻居信息到来*/  
          have_msap = 1;  
          }  
    …  
    …  
    if(have_msap)  
          {  
    #ifndef WIN32  
            #warning We need to verify whether this is actually the case.  
    #endif // WIN32  
        lldp_port->rxChanges = TRUE;  
          
        debug_printf(DEBUG_TLV, "We have a(n) %d byte MSAP!\n", msap_length);  
    /* 
    创建一条新的保存邻居信息的lldp_msap结构体,并将之前缓存的tlv信息复制到其中。然后更新该端口对应的lldp_port结构体中的lldp_msap信息。亦即更新该底层端口对应的邻居信息。 
    */  
        msap_cache = calloc(1, sizeof(struct lldp_msap));  
      
        msap_cache->id = msap_id;  
        msap_cache->length = msap_length;  
        msap_cache->tlv_list = tlv_list;  
        msap_cache->next = NULL;  
      
        msap_cache->ttl_tlv = msap_ttl_tlv;  
        msap_ttl_tlv = NULL;  
      
        //debug_printf(DEBUG_MSAP, "Iterating MSAP Cache...\n");  
      
        //iterate_msap_cache(msap_cache);  
      
        //debug_printf(DEBUG_MSAP, "Updating MSAP Cache...\n");  
      
        debug_printf(DEBUG_MSAP, "Setting rxInfoTTL to: %d\n", lldp_port->rx.timers.rxTTL);  
      
        msap_cache->rxInfoTTL = lldp_port->rx.timers.rxTTL;  
      
        update_msap_cache(lldp_port, msap_cache);  
      
        if(msap_tlv1 != NULL) {  
          debug_printf(DEBUG_NORMAL, "Error: msap_tlv1 is still allocated!\n");  
          free(msap_tlv1);  
          msap_tlv1 = NULL;  
        }  
      
        if(msap_tlv2 != NULL) {  
          debug_printf(DEBUG_NORMAL, "Error: msap_tlv2 is still allocated!\n");  
          free(msap_tlv2);  
          msap_tlv2 = NULL;  
        }  
      
          }  
        else  
          {  
        debug_printf(DEBUG_NORMAL, "[ERROR] No MSAP for TLVs in Frame!\n");  
          }  
      
        /* Report frame errors */  
        if(badFrame) {  
            rxBadFrameInfo(badFrame);  
        }  
      
        return badFrame;  
    }  

LLDP接收状态机

void rxStatemachineRun(struct lldp_port *lldp_port)
{
  debug_printf(DEBUG_NORMAL, "Running RX state machine for %s\n", lldp_port->if_name);

    rxGlobalStatemachineRun(lldp_port);

    switch(lldp_port->rx.state)
      {
      case LLDP_WAIT_PORT_OPERATIONAL: //空操作
    {
      // Do nothing here... we'll transition in the global state machine check
      rx_do_lldp_wait_port_operational(lldp_port);
    }break;
      case DELETE_AGED_INFO:  //老化邻居,以及清理
    {
      rx_do_delete_aged_info(lldp_port);
    }break;
      case RX_LLDP_INITIALIZE: //初始化remote mib
    {
      rx_do_rx_lldp_initialize(lldp_port);
    }break;
      case RX_WAIT_FOR_FRAME: //等待LLDP消息
    {
      rx_do_rx_wait_for_frame(lldp_port);
    }break;
      case RX_FRAME:   //接收到LLDP消息,并更新remote MIB
    {
      rx_do_rx_frame(lldp_port);
    }break;
      case DELETE_INFO: {
    rx_do_rx_delete_info(lldp_port);   //清理老化邻居
      }break;
      case UPDATE_INFO: {
    rx_do_rx_update_info(lldp_port);
      }break;
      default:
    debug_printf(DEBUG_NORMAL, "[ERROR] The RX State Machine is broken!\n");      
    };
    rx_do_update_timers(lldp_port);  //更新时间
}

发送LLDP处理

发送LLDP全是按照下面的伪代码组织

  • 对于每个接口在对应的状况下调用对应的tx_do_tx_something_frame,something是该状态对应的行为。
  • 该函数将首先构建LLDP然后使用txFrame发送
void tx_do_tx_something_frame(struct lldp_port *lldp_port) {
    /* As per 802.1AB 10.5.4.3 */
    mibConstrsomethingLLDPDU(lldp_port);
    txFrame(lldp_port);
}

LLDP发送状态机

void txStatemachineRun(struct lldp_port *lldp_port)
{
    debug_printf(DEBUG_STATE, "%s -> %s\n", lldp_port->if_name, txStateFromID(lldp_port->tx.state));

    txGlobalStatemachineRun(lldp_port);

    switch(lldp_port->tx.state)
    {
        case TX_LLDP_INITIALIZE: //初始化时设置部分默认参数
            {
                tx_do_tx_lldp_initialize(lldp_port);
            }break;
        case TX_IDLE:      
            {
                tx_do_tx_idle(lldp_port); //idle不作为
            }break;
        case TX_SHUTDOWN_FRAME: //接口关闭,发送空TLV通告
            {
                tx_do_tx_shutdown_frame(lldp_port); 
            }break;
        case TX_INFO_FRAME: //LLPD通告本机信息
            {
                tx_do_tx_info_frame(lldp_port);
            }break;
        default:
            debug_printf(DEBUG_NORMAL, "[ERROR] The TX State Machine is broken!\n");
    };
    tx_do_update_timers(lldp_port);//仅仅更新本机接口时间
}

顶层代码

ServiceMain模块

主要做了2件事

  • 运行状态机
  • 建立本地套接字,为其他进程与openlldp交互预留接口
#ifdef BUILD_SERVICE
// We are building as a service, so this should be our ServiceMain()
int ServiceMain(int argc, char *argv[])
#else
int main(int argc, char *argv[])
#endif // BUILD_SERVICE
{
#ifndef WIN32
    uid_t uid;
    struct timeval timeout;
    struct timeval un_timeout;
    int fork = 1;
#endif // WIN32
    int op = 0;
    char *theOpts = "i:d:fshl:o";
    int socket_width = 0;
    time_t current_time = 0;
    time_t last_check = 0;
    int result = 0;
    struct lldp_port *lldp_port = NULL;

#ifdef BUILD_SERVICE
   ServiceStatus.dwServiceType = 
      SERVICE_WIN32; 
   ServiceStatus.dwCurrentState = 
      SERVICE_START_PENDING; 
   ServiceStatus.dwControlsAccepted   =  
      SERVICE_ACCEPT_STOP | 
      SERVICE_ACCEPT_SHUTDOWN;
   ServiceStatus.dwWin32ExitCode = 0; 
   ServiceStatus.dwServiceSpecificExitCode = 0; 
   ServiceStatus.dwCheckPoint = 0; 
   ServiceStatus.dwWaitHint = 0; 
 
   hStatus = RegisterServiceCtrlHandler(
      "OpenLLDP", 
      (LPHANDLER_FUNCTION)ControlHandler); 
   if (hStatus == (SERVICE_STATUS_HANDLE)0) 
   { 
      // Registering Control Handler failed
      return -1; 
   }  
/*  ServiceStatus.dwCurrentState = SERVICE_STOPPED; 
    ServiceStatus.dwWin32ExitCode = 0xfe; 
    SetServiceStatus(hStatus, &ServiceStatus);*/
#endif  // BUILD_SERVICE

    program = argv[0];

#ifndef WIN32
    //获取传入参数
    // Process any arguments we were passed in.
    while ((op = getopt(argc, argv, theOpts)) != EOF) {
      switch (op) {
      case 'd':
    // Set the debug level.
    if ((atoi(optarg) == 0) && (optarg[0] != '0')) {
      debug_alpha_set_flags(optarg);
    } else {
      debug_set_flags(atoi(optarg));
    }
    break;
      case 'i':
    iface_filter = 1;
    memcpy(iface_list, optarg, strlen(optarg));
    iface_list[IF_NAMESIZE - 1] = '\0';
    debug_printf(DEBUG_NORMAL, "Using interface %s\n", iface_list);
    break;
      case 'l':
#ifdef USE_CONFUSE
    lci.config_file = optarg;
#else
    debug_printf(DEBUG_NORMAL, "OpenLLDP wasn't compiled with libconfuse support.\n");
    exit(1);
#endif // USE_CONFUSE
    break;
      case 'o':
    // Turn on the looback interface. :)
    process_loopback = 1;
    break;
      case 'f':
    fork = 0;
    break;
      case 's':
    unlink(local.sun_path);
    break;
      case 'h':
      default:
    usage();
    exit(0);
    break;
      };
    }
    //建立域流套接字,为本地其他进程数据
    neighbor_local_sd = socket(AF_UNIX, SOCK_STREAM, 0);

    if(neighbor_local_sd < 0)
      {
    debug_printf(DEBUG_NORMAL, "[Error] Unable to open unix domain socket for client registration!\n");
      }

    local.sun_family = AF_UNIX;

    strcpy(local.sun_path, "/var/run/lldpd.sock");

    debug_printf(DEBUG_NORMAL, "%s:%d\n", local.sun_path, strlen(local.sun_path));
 
    // Bind to the neighbor list socket.
    result = bind(neighbor_local_sd, (struct sockaddr *)&local, sizeof(local));

    if(result != 0) {
      debug_printf(DEBUG_NORMAL, "[Error] Unable to bind to the unix domain socket for client registration!\n");
    }
    //监听
    result = listen(neighbor_local_sd, 5);

    if(result != 0) {
      debug_printf(DEBUG_NORMAL, "[Error] Unable to listen to the unix domain socket for client registration!\n");
    }

    // Set the socket permissions
    if(chmod("/var/run/lldpd.sock", S_IWOTH) != 0) {
      debug_printf(DEBUG_NORMAL, "[Error] Unable to set permissions for domain socket!\n");
    }

    /* Needed for select() */
    fd_set readfds;

    fd_set unixfds;

    // get uid of user executing program. 
    uid = getuid();

    if (uid != 0) {
        debug_printf(DEBUG_NORMAL, "You must be running as root to run %s!\n", program);
        exit(0);
    }

#endif // WIN32

    lci.config_file = NULL;

    /* Initialize2 the LLDP subsystem */
    /* This should happen on a per-interface basis */
    if(initializeLLDP() == 0) {
        debug_printf(DEBUG_NORMAL, "No interface found to listen on\n");
    }

    get_sys_desc();
    get_sys_fqdn();

    #ifdef USE_CONFUSE
    //read the location config file for the first time!
    lci_config ();
    #endif // USE_CONFUSE

#ifdef BUILD_SERVICE
     // We report the running status to SCM. 
   ServiceStatus.dwCurrentState = 
      SERVICE_RUNNING; 
   SetServiceStatus (hStatus, &ServiceStatus);

   while (ServiceStatus.dwCurrentState == 
          SERVICE_RUNNING)
   {
       // Sleep for 1 seconds.
       Sleep(1000);
   }
#endif // BUILD_SERVICE

#ifndef WIN32

   if (fork) {
     if (daemon(0,0) != 0)
       debug_printf(DEBUG_NORMAL, "Unable to daemonize (%m) at: %s():%d\n",
            __FUNCTION__, __LINE__);
   }
#endif // WIN32

    while(1) {

#ifndef WIN32
      /* Set up the neighbor client domain socket */
      if(neighbor_local_sd > 0) {
    FD_ZERO(&unixfds);  
    FD_SET(neighbor_local_sd, &unixfds);
      } else {
    debug_printf(DEBUG_NORMAL, "Couldn't initialize the socket (%d)\n", neighbor_local_sd);
      }

        /* Set up select() */
        FD_ZERO(&readfds);
#endif // WIN32

        lldp_port = lldp_ports;

        while(lldp_port != NULL) {
            // This is not the interface you are looking for...
            if(lldp_port->if_name == NULL)
            {
                debug_printf(DEBUG_NORMAL, "[ERROR] Interface index %d with name is NULL at: %s():%d\n", lldp_port->if_index, __FUNCTION__, __LINE__);
                continue;
            }

#ifndef WIN32
            FD_SET(lldp_port->socket, &readfds);

            if(lldp_port->socket > socket_width)
            {
                socket_width = lldp_port->socket;
            }
#endif

            lldp_port = lldp_port->next;

        }

        time(&current_time);

#ifndef WIN32
        // Will be used to tell select how long to wait for...
        timeout.tv_sec = 1;
        timeout.tv_usec = 0;

        // Timeout after 1 second if nothing is ready
        result = select(socket_width+1, &readfds, NULL, NULL, &timeout);
#endif // WIN32

        // Everything is cool... process the sockets
        lldp_port = lldp_ports;

        while(lldp_port != NULL) {
            // This is not the interface you are looking for...
            if(lldp_port->if_name == NULL) {
                debug_printf(DEBUG_NORMAL, "[ERROR] Interface index %d with name is NULL at: %s():%d\n", lldp_port->if_index, __FUNCTION__, __LINE__);
                continue;
            }

#ifndef WIN32
            if(result > 0) {
                if(FD_ISSET(lldp_port->socket, &readfds)) {
                    debug_printf(DEBUG_INT, "%s is readable!\n", lldp_port->if_name);

                    // Get the frame back from the OS-specific frame handler.
                    lldp_read(lldp_port);

                    if(lldp_port->rx.recvsize <= 0) {
                        if(errno != EAGAIN && errno != ENETDOWN) {
                            printf("Error: (%d) : %s (%s:%d)\n", errno, strerror(errno), __FUNCTION__, __LINE__);
                        }
                    } else {
                        debug_printf(DEBUG_INT, "Got an LLDP frame %d bytes long on %s!\n", lldp_port->rx.recvsize, lldp_port->if_name);

                        //      debug_hex_dump(DEBUG_INT, lldp_port->rx.frame, lldp_port->rx.recvsize);

                        // Mark that we received a frame so the state machine can process it.
                        lldp_port->rx.rcvFrame = 1;

                        rxStatemachineRun(lldp_port);
                    }
                }

            }
#endif // WIN32
            if((result == 0) || (current_time > last_check)) {
                lldp_port->tick = 1;

                txStatemachineRun(lldp_port);  //运行发送状态机
                rxStatemachineRun(lldp_port); //运行接收状态机

#ifndef WIN32
        // Will be used to tell select how long to wait for...
        un_timeout.tv_sec  = 0;
        un_timeout.tv_usec = 2;

        result = select(neighbor_local_sd+1, &unixfds, NULL, NULL, &un_timeout);

        if(result > 0) {
          if(FD_ISSET(neighbor_local_sd, &unixfds)) {
            debug_printf(DEBUG_NORMAL, "Got a request on the unix domain socket!\n");

            socklen_t addrlen = sizeof(remote);

            neighbor_remote_sd = accept(neighbor_local_sd, (struct sockaddr *)&remote, &addrlen);
            
            if(neighbor_remote_sd < 0) {
              debug_printf(DEBUG_NORMAL, "Couldn't accept remote client socket!\n");
            } else {            
              char *client_msg = lldp_neighbor_information(lldp_ports);
              int bytes_tx = strlen(client_msg);
              int bytes_sent = 0;

              while(bytes_tx > 0) {
            
            debug_printf(DEBUG_NORMAL, "Transmitting %d bytes to client...\n", bytes_tx);
            
            bytes_sent = send(neighbor_remote_sd, client_msg, strlen(client_msg), 0);

            debug_printf(DEBUG_NORMAL, "%d bytes left to send.  Bytes already sent: %d\n\n", bytes_tx, bytes_sent);
               
            bytes_tx -= bytes_sent;

              }         

              free(client_msg);
              close(neighbor_remote_sd);              
            }
          }

        }
#endif // WIN32

                lldp_port->tick = 0;
            }

            if(result < 0) {
                if(errno != EINTR) {
                    debug_printf(DEBUG_NORMAL, "[ERROR] %s\n", strerror(errno));
                }
            }

            lldp_port = lldp_port->next;

        }

        time(&last_check);     
    }

    return 0;
}

lldpneighbors模块

主要是演示如何从ServiceMain获取主机的邻居信息。

 * 
 * OpenLLDP Neighbor  
 * 
 * See LICENSE file for more info. 
 *  
 * File: lldpneighbors.c 
 *  
 * Authors: Terry Simons (terry.simons@gmail.com) 
 * 
 *******************************************************************/  
  
#ifndef WIN32  
#include <sys/types.h>  
#include <sys/socket.h>  
#include <sys/un.h>  
#include <errno.h>  
#endif  
  
#include <unistd.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <sys/types.h>  
#include <string.h>  
  
#define NEIGHBORLIST_SIZE 512  
#define DEBUG 0  
  
int main(int argc, char *argv[]) {     
  char msg[NEIGHBORLIST_SIZE];  
  char *buf            = NULL;  
  char *tmp            = NULL;  
  int s                = 0;  
  unsigned int msgSize = 0;  
  size_t bytes         = 0;  
  int result           = 0;  
  
  buf = calloc(1, NEIGHBORLIST_SIZE);  
  
  memset(&msg[0], 0x0, NEIGHBORLIST_SIZE);  
  
#ifndef WIN32  
  struct sockaddr_un addr;  
  
  s = socket(AF_UNIX, SOCK_STREAM, 0);  
  
  addr.sun_family = AF_UNIX;  
  /* 
    使用和lldp_main模块一样的unix套接字对象标识 
  */  
  strcpy(addr.sun_path, "/var/run/lldpd.sock");  
  /* 
    连接 
  */  
  result = connect(s, (struct sockaddr *)&addr, sizeof(addr));  
  
  if(result < 0) {  
    printf("\n%s couldn't connect to the OpenLLDP transport socket.  Is lldpd running?\n", argv[0]);  
      
    goto cleanup;  
  }  
  
  /* 
    接收 
  */  
  while((bytes = recv(s, msg, NEIGHBORLIST_SIZE, 0))) {  
      
    if(bytes > 0) {  
        
      tmp = calloc(1, msgSize + bytes + 1);  
  
      if(buf != NULL) {  
    memcpy(tmp, buf, msgSize);  
    free(buf);  
    buf = NULL;  
      }  
        
      memcpy(&tmp[msgSize], msg, bytes);  
      msgSize += bytes;  
        
      buf = tmp;  
      tmp = NULL;  
    } else {  
      if(DEBUG) {  
    printf("Error reading %d bytes: %d:%s\n", NEIGHBORLIST_SIZE, errno, strerror(errno));  
      }  
    }  
      
    if(DEBUG) {  
      printf("Read %d bytes.  Total size is now: %d\n", (int)bytes, msgSize);  
      printf("Buffer is: 0x%08X and Temporary Buffer is 0x%08X.\n", (int)&buf, (int)&tmp);  
    }  
  }  
  
  if(buf != NULL) {  
    printf("%s\n", buf);  
  }  
  
    
 cleanup:  
  if(buf != NULL)  
    {  
      free(buf);  
      msgSize = 0;  
      buf = NULL;  
    }  
  
  close(s);  
#endif  
  
  return 0;  
}  

参考文章

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容