項目簡介
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中。
詳細結構
如圖2所示,具體來組織方式是4層鏈表嵌套.
- 第一層是lldp_port, 是將各個接口的管理結構鏈式管理.
- 第二層是lldp_masp , 其存放的是各個接口相鄰設備的remote MIB。
- 第三層是lldp_tlv_list, 每一個管理一個相鄰設備的tlv。
具體代碼分析
了解了上面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(¤t_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;
}