OpenLLDP 源碼分析

項目簡介

  • LLDP協議由802.1ab所定義。它是一個二層協議,一般稱之為數據鏈路發現協議。這里對于該協議的實現原理不做詳細介紹。具體原理可以參見IEEE 802.1ab文檔連接

  • OpenLLDP為802.1ab的開源實現,號稱支Linux,macOS,FreeBSD,NetBSD等眾多類unix系統。參見維基百科對于openlldp的介紹。

  • 下面為OpenLLDP的
    項目主頁
    sourceforge頁面。

  • 這里要提前指出的是,OpenLLDP的實現的并非非常標準的lldp??梢哉f它只是簡單的實現了最基本的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組成?;旧喜煌泥従有畔胁煌膌ldp_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閱讀 230,362評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,577評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,486評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,852評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,600評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,944評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,944評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,108評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,652評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,385評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,616評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,111評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,798評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,205評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,537評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,334評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,570評論 2 379

推薦閱讀更多精彩內容