From 86d7ae826f4ff37435df1f48ca7a2d76f4e86d45 Mon Sep 17 00:00:00 2001 From: Georgy Kirichenko Date: Thu, 6 Jun 2024 22:56:00 +0300 Subject: [PATCH 1/6] WIP: bird import proto --- common/icp.h | 4 +- controlplane/libbird.cpp | 682 +++++++++++++++++++++++++++++++++++++++ controlplane/libbird.h | 187 +++++++++++ controlplane/meson.build | 3 +- controlplane/rib.cpp | 21 ++ controlplane/rib.h | 1 + 6 files changed, 896 insertions(+), 2 deletions(-) create mode 100644 controlplane/libbird.cpp create mode 100644 controlplane/libbird.h diff --git a/common/icp.h b/common/icp.h index d5103a29..b8b6f2ea 100644 --- a/common/icp.h +++ b/common/icp.h @@ -671,7 +671,9 @@ using eor = std::tuple; ///< table_name -using request = std::vector>; +using action = std::variant; + +using request = std::vector; } namespace rib_summary diff --git a/controlplane/libbird.cpp b/controlplane/libbird.cpp new file mode 100644 index 00000000..ecffeec4 --- /dev/null +++ b/controlplane/libbird.cpp @@ -0,0 +1,682 @@ +#include "libbird.h" + +#include "common/icp.h" +#include "common/type.h" +#include "controlplane/rib.h" +#include + +static inline bool +decode_u8(uintptr_t* ppos, uintptr_t end, uint8_t* pvalue) +{ + if (*ppos + sizeof(uint8_t) > end) + return false; + *pvalue = *(uint8_t*)*ppos; + *ppos += sizeof(uint8_t); + return true; +} + +static inline bool +decode_u16(uintptr_t* ppos, uintptr_t end, uint16_t* pvalue) +{ + if (*ppos + sizeof(uint16_t) > end) + return false; + *pvalue = *(uint16_t*)*ppos; + *ppos += sizeof(uint16_t); + return true; +} + +static inline bool +decode_u32(uintptr_t* ppos, uintptr_t end, uint32_t* pvalue) +{ + if (*ppos + sizeof(uint32_t) > end) + return false; + *pvalue = *(uint32_t*)*ppos; + *ppos += sizeof(uint32_t); + return true; +} + +static inline bool +decode_chunk(uintptr_t* ppos, uintptr_t end, uintptr_t* pchunk_end) +{ + uint32_t chunk_size; + if (!decode_u32(ppos, end, &chunk_size)) + return false; + *pchunk_end = *ppos + chunk_size; + return *pchunk_end <= end; +} + +static inline bool +decode_u32_array(uintptr_t* ppos, uintptr_t end, uintptr_t* parray_end) +{ + uint32_t array_count; + if (!decode_u32(ppos, end, &array_count)) + return false; + *parray_end = *ppos + sizeof(uint32_t) * array_count; + return (*parray_end <= end); +} + +static inline bool +decode_ip_addr(uintptr_t* ppos, uintptr_t end, ip_addr* paddr) +{ + if (*ppos + sizeof(ip_addr) > end) + return false; + *paddr = *(ip_addr*)*ppos; + *ppos += sizeof(ip_addr); + return true; +} + +static inline bool +decode_net_addr(uintptr_t* ppos, uintptr_t end, net_addr_union* paddr) +{ + if (*ppos + sizeof(net_addr_union) > end) + return false; + *paddr = *(net_addr_union*)*ppos; + // Some members of net_addr_union are variable length structures so + // check the length attribute. + if (*ppos + paddr->n.length > end) + return false; + if (paddr->n.length <= sizeof(net_addr_union)) + { + *ppos += sizeof(net_addr_union); + return true; + } + return false; + /* + * FIXME: this is not supported yet + memcpy( + paddr + 1, (void *)(*ppos + sizeof(net_addr_union)), + paddr->n.length - sizeof(net_addr_union) + ); + *ppos += paddr->n.length; + return true; + */ +} + +static inline bool +decode_u16_array(uintptr_t* ppos, uintptr_t end, std::vector* values) +{ + size_t attr_end; + if (!decode_chunk(ppos, end, &attr_end)) + { + return false; + } + + while (*ppos < attr_end) + { + uint16_t value; + if (!decode_u16(ppos, attr_end, &value)) + { + return false; + } + + values->emplace_back(value); + } + + return true; +} + +static inline bool +decode_u32_array(uintptr_t* ppos, uintptr_t end, std::vector* values) +{ + size_t attr_end; + if (!decode_chunk(ppos, end, &attr_end)) + { + return false; + } + + while (*ppos < attr_end) + { + uint32_t value; + if (!decode_u32(ppos, attr_end, &value)) + { + return false; + } + + values->emplace_back(value); + } + + return true; +} + +static inline bool +decode_as_path(uintptr_t* ppos, uintptr_t end, std::vector* as_path) +{ + size_t attr_end; + if (!decode_chunk(ppos, end, &attr_end)) + { + return false; + } + + while (*ppos < attr_end) + { + uint8_t segment_type; + if (!decode_u8(ppos, attr_end, &segment_type)) + { + return false; + } + + if (segment_type != 2 && segment_type != 3) + { + return false; + } + + uint8_t segment_len; + if (!decode_u8(ppos, attr_end, &segment_len)) + { + return false; + } + + for (uint16_t idx = 0; idx < segment_len; ++idx) + { + uint32_t as; + if (!decode_u32(ppos, attr_end, &as)) + { + return false; + } + as = ntohl(as); + as_path->emplace_back(as); + } + } + + return true; +} + +static inline bool +ipa_is_ip4(ip_addr addr) +{ + return addr.addr[0] == 0 && addr.addr[1] == 0 && addr.addr[2] == 0xffff; +} + +static inline common::ipv4_address_t +ipa_to_address_4(ip_addr addr) +{ + return common::ipv4_address_t(addr.addr[3]); +} + +static inline common::ipv6_address_t +ipa_to_address_6(ip_addr addr) +{ + addr.addr[0] = ntohl(addr.addr[0]); + addr.addr[1] = ntohl(addr.addr[1]); + addr.addr[2] = ntohl(addr.addr[2]); + addr.addr[3] = ntohl(addr.addr[3]); + return common::ipv6_address_t((uint8_t*)&addr); +} + +static inline common::ip_address_t +ipa_to_address(ip_addr addr) +{ + if (ipa_is_ip4(addr)) + return common::ip_address_t(ipa_to_address_4(addr)); + return common::ip_address_t(ipa_to_address_6(addr)); +} + +static inline bool +recover_prefix_info(net_addr_union* paddr, common::ip_prefix_t* prefix, common::ip_address_t* vpnDST, uint32_t* vpnRD) +{ + + switch (paddr->n.type) + { + case NET_IP4: + { + *prefix = common::ip_prefix_t(common::ipv4_address_t(paddr->ip4.prefix), paddr->ip4.pxlen); + break; + } + + case NET_IP6: + { + *prefix = common::ip_prefix_t(ipa_to_address_6(paddr->ip6.prefix), paddr->ip6.pxlen); + break; + } + + case NET_VPN4: + { + if ((paddr->vpn4.rd >> 48) != 1) + { + return false; + } + + *prefix = common::ip_prefix_t(common::ipv4_address_t(paddr->vpn4.prefix), paddr->vpn4.pxlen); + *vpnDST = common::ip_address_t(common::ipv4_address_t(paddr->vpn4.rd >> 16)); + *vpnRD = paddr->vpn4.rd & 0xffff; + break; + } + + case NET_VPN6: + { + if ((paddr->vpn6.rd >> 48) != 1) + { + return false; + } + + *prefix = common::ip_prefix_t(ipa_to_address_6(paddr->vpn6.prefix), paddr->vpn6.pxlen); + *vpnDST = common::ip_address_t(common::ipv4_address_t(paddr->vpn4.rd >> 16)); + *vpnRD = paddr->vpn6.rd & 0xffff; + + break; + } + + default: + return false; + } + + return true; +} + +static inline bool +recover_next_hop(uintptr_t* ppos, uintptr_t end, common::ip_address_t* next_hop) +{ + if (end - *ppos != sizeof(ip_addr) && + end - *ppos != 2 * sizeof(ip_addr)) + { + return false; + } + + if (end - *ppos == sizeof(ip_addr)) + { + ip_addr nh_addr; + if (!decode_ip_addr(ppos, end, &nh_addr)) + { + return false; + } + *next_hop = ipa_to_address(nh_addr); + return true; + } + + ip_addr nh_addr1; + ip_addr nh_addr2; + if (!decode_ip_addr(ppos, end, &nh_addr1) || + !decode_ip_addr(ppos, end, &nh_addr2)) + { + return false; + } + + if (nh_addr2.addr[0] == 0 && nh_addr2.addr[1] == 0 && + nh_addr2.addr[2] == 0 && nh_addr2.addr[3] == 0) + { + *next_hop = ipa_to_address(nh_addr1); + } + else + { + *next_hop = ipa_to_address(nh_addr2); + } + + return true; +} + +static bool +parse_route_update(uintptr_t* ppos, uintptr_t end, const char* vrf, common::icp::rib_update::action* paction) +{ + common::ip_address_t peer_address; + std::string attribute_origin; + std::string path_information; + std::vector attribute_aspath; + std::set attribute_communities; + std::set attribute_large_communities; + std::vector labels; + + uint32_t attribute_local_preference = 0; + uint32_t attribute_med = 0; + + common::ip_address_t next_hop; + common::ip_prefix_t prefix; + common::ip_address_t vpnDST; + uint32_t vpnRD = 0; + + /* Decode route prefix. */ + net_addr_union addr; + if (!decode_net_addr(ppos, end, &addr)) + return false; + + if (!recover_prefix_info(&addr, &prefix, &vpnDST, &vpnRD)) + return false; + + uint32_t type; + if (!decode_u32(ppos, end, &type)) + return false; + + ip_addr remote_addr; + if (!decode_ip_addr(ppos, end, &remote_addr)) + return false; + peer_address = ipa_to_address(remote_addr); + + size_t attrs_end; + if (!decode_chunk(ppos, end, &attrs_end)) + return false; + + if (attrs_end != end) + return false; + + /* Now decode all attributes one by one. */ + while (*ppos < attrs_end) + { + uint32_t attr_id; + if (!decode_u32(ppos, attrs_end, &attr_id)) + return false; + + switch (EA_ID(attr_id)) + { + case BA_ORIGIN: + { + uint32_t origin; + if (!decode_u32(ppos, attrs_end, &origin)) + { + return false; + } + switch (origin) + { + case 0: + attribute_origin = "IGP"; + break; + case 1: + attribute_origin = "EGP"; + break; + case 2: + attribute_origin = "Incomplete"; + break; + default: + attribute_origin = "?"; + } + + break; + } + + case BA_MULTI_EXIT_DISC: + { + uint32_t med; + if (!decode_u32(ppos, attrs_end, &med)) + { + return false; + } + + attribute_med = med; + + break; + } + + case BA_LOCAL_PREF: + { + uint32_t pref; + if (!decode_u32(ppos, attrs_end, &pref)) + { + return false; + } + + attribute_local_preference = pref; + + break; + } + + case BA_ORIGINATOR_ID: + { + uint32_t originator; + if (!decode_u32(ppos, attrs_end, &originator)) + { + return false; + } + + break; + } + + case BA_AS_PATH: + { + if (!decode_as_path(ppos, attrs_end, &attribute_aspath)) + { + return false; + } + + break; + } + + case BA_NEXT_HOP: + { + size_t attr_end; + if (!decode_chunk(ppos, attrs_end, &attr_end)) + { + return false; + } + + if (!recover_next_hop(ppos, attr_end, &next_hop)) + { + return false; + } + + break; + } + + case BA_COMMUNITY: + { + std::vector communities; + if (!decode_u16_array(ppos, attrs_end, &communities)) + { + return false; + } + + if (communities.size() % 2 != 0) + { + return false; + } + + for (size_t idx = 0; idx < communities.size() / 2; ++idx) + { + attribute_communities.emplace( + communities[idx * 2 + 0], + communities[idx * 2 + 1]); + } + + break; + } + + case BA_CLUSTER_LIST: + { + std::vector clusters; + if (!decode_u32_array(ppos, attrs_end, &clusters)) + { + return false; + } + + break; + } + + case BA_EXT_COMMUNITY: + { + std::vector ext_communities; + if (!decode_u32_array(ppos, attrs_end, &ext_communities)) + { + return false; + } + + break; + } + + case BA_LARGE_COMMUNITY: + { + std::vector large_communities; + if (!decode_u32_array(ppos, attrs_end, &large_communities)) + { + return false; + } + + if (large_communities.size() % 3 != 0) + { + return false; + } + + for (size_t idx = 0; idx < large_communities.size() / 3; + ++idx) + { + + attribute_large_communities.emplace( + large_communities[idx * 3 + 0], + large_communities[idx * 3 + 1], + large_communities[idx * 3 + 2]); + } + + break; + } + + case BA_MPLS_LABEL_STACK: + { + if (!decode_u32_array(ppos, attrs_end, &labels)) + { + return false; + } + + break; + } + + default: + return false; + } + } + + std::string afi; + std::string safi; + + // FIXME: table name + if (prefix.is_ipv4()) + { + afi = "ipv4"; + } + else + { + afi = "ipv6"; + } + + if (vpnRD == 0) + { + safi = "unicast"; + } + else + { + safi = "mpls-vpn"; + } + + if (type == 1) + { + common::icp::rib_update::insert insert = {"bgp", + std::string(vrf), + YANET_RIB_PRIORITY_DEFAULT, + {}}; + + auto& announce = std::get<3>(insert)[{peer_address, + attribute_origin, + attribute_med, + attribute_aspath, + attribute_communities, + attribute_large_communities, + attribute_local_preference}]; + + auto& announce_table = announce[afi + " " + safi]; + announce_table[next_hop].emplace_back(prefix, std::to_string(vpnRD), labels); + + *paction = insert; + } + else + { + common::icp::rib_update::remove remove = {"bgp", + std::string(vrf), + YANET_RIB_PRIORITY_DEFAULT, + {}}; + + auto& announce = std::get<3>(remove)[peer_address]; + + // FIXME: table name + auto& announce_table = announce[afi + " " + safi]; + announce_table.emplace_back(prefix, std::to_string(vpnRD), labels); + + *paction = remove; + } + + return true; +} + +void read_bird_feed(const char* sock_name, const char* vrf, class rib_t* rib) +{ + /* Connect to bird export socket. */ + int bird_sock = socket(AF_UNIX, SOCK_STREAM, 0); + struct sockaddr_un server_addr; + server_addr.sun_family = AF_UNIX; + strncpy(server_addr.sun_path, sock_name, sizeof(server_addr.sun_path) - 1); + if (connect(bird_sock, + (struct sockaddr*)&server_addr, + sizeof(server_addr)) != 0) + { + return; + } + + /* + * Read buffer is used to accumulate multuple route updates and + * flush then at once. 1Mib looks good as the buffur size. + */ + const size_t buf_size = 1 << 20; + void* read_buf = malloc(buf_size); + if (read_buf == NULL) + { + close(bird_sock); + return; + } + /* + * The variable is the offset to read data to and denotes the first + * byte which could nit be parsed yet. + */ + size_t read_pos = 0; + /* + * Parse position is the position of the next one route update to + * process. + */ + size_t parse_pos = 0; + + common::icp::rib_update::request requests; + + for (;;) + { + /* Read as mush as possible data */ + ssize_t readen = read(bird_sock, + (void*)((uintptr_t)read_buf + read_pos), + buf_size - read_pos); + if (readen <= 0) + { + break; + } + + /* Adust read postion and try to recover next route update. */ + read_pos += readen; + while (parse_pos < read_pos) + { + /* pos and end denote addresses of a memory chunk to parse. */ + uintptr_t pos = (uintptr_t)read_buf + parse_pos; + uintptr_t end = (uintptr_t)read_buf + read_pos; + + /* Determine boundaries of the next route update. */ + uintptr_t route_end; + if (!decode_chunk(&pos, end, &route_end)) + break; + + common::icp::rib_update::action action; + if (!parse_route_update(&pos, route_end, vrf, &action)) + break; + + requests.emplace_back(action); + + parse_pos = pos - (uintptr_t)read_buf; + } + + rib->rib_update(requests); + rib->rib_flush(); + + if (buf_size - read_pos < buf_size / 2) + { + memmove(read_buf, + (void*)((uintptr_t)read_buf + parse_pos), + read_pos - parse_pos); + read_pos = read_pos - parse_pos; + parse_pos = 0; + } + } + + free(read_buf); + close(bird_sock); + + return; +} diff --git a/controlplane/libbird.h b/controlplane/libbird.h new file mode 100644 index 00000000..c10ba54e --- /dev/null +++ b/controlplane/libbird.h @@ -0,0 +1,187 @@ +#ifndef LIBBIRD_H +#define LIBBIRD_H + +#include + +#define NET_IP4 1 +#define NET_IP6 2 +#define NET_VPN4 3 +#define NET_VPN6 4 +#define NET_ROA4 5 +#define NET_ROA6 6 +#define NET_FLOW4 7 +#define NET_FLOW6 8 +#define NET_IP6_SADR 9 +#define NET_MPLS 10 +#define NET_MAX 11 + +#define NB_IP4 (1 << NET_IP4) +#define NB_IP6 (1 << NET_IP6) +#define NB_VPN4 (1 << NET_VPN4) +#define NB_VPN6 (1 << NET_VPN6) +#define NB_ROA4 (1 << NET_ROA4) +#define NB_ROA6 (1 << NET_ROA6) +#define NB_FLOW4 (1 << NET_FLOW4) +#define NB_FLOW6 (1 << NET_FLOW6) +#define NB_IP6_SADR (1 << NET_IP6_SADR) +#define NB_MPLS (1 << NET_MPLS) + +#define NB_IP (NB_IP4 | NB_IP6) +#define NB_VPN (NB_VPN4 | NB_VPN6) +#define NB_ROA (NB_ROA4 | NB_ROA6) +#define NB_FLOW (NB_FLOW4 | NB_FLOW6) +#define NB_DEST (NB_IP | NB_IP6_SADR | NB_VPN | NB_MPLS) +#define NB_ANY 0xffffffff + +#define BA_ORIGIN 0x01 /* RFC 4271 */ /* WM */ +#define BA_AS_PATH 0x02 /* WM */ +#define BA_NEXT_HOP 0x03 /* WM */ +#define BA_MULTI_EXIT_DISC 0x04 /* ON */ +#define BA_LOCAL_PREF 0x05 /* WD */ +#define BA_ATOMIC_AGGR 0x06 /* WD */ +#define BA_AGGREGATOR 0x07 /* OT */ +#define BA_COMMUNITY 0x08 /* RFC 1997 */ /* OT */ +#define BA_ORIGINATOR_ID 0x09 /* RFC 4456 */ /* ON */ +#define BA_CLUSTER_LIST 0x0a /* RFC 4456 */ /* ON */ +#define BA_MP_REACH_NLRI 0x0e /* RFC 4760 */ +#define BA_MP_UNREACH_NLRI 0x0f /* RFC 4760 */ +#define BA_EXT_COMMUNITY 0x10 /* RFC 4360 */ +#define BA_AS4_PATH 0x11 /* RFC 6793 */ +#define BA_AS4_AGGREGATOR 0x12 /* RFC 6793 */ +#define BA_AIGP 0x1a /* RFC 7311 */ +#define BA_LARGE_COMMUNITY 0x20 /* RFC 8092 */ +#define BA_ONLY_TO_CUSTOMER 0x23 /* RFC 9234 */ + +/* Bird's private internal BGP attributes */ +#define BA_MPLS_LABEL_STACK 0xfe /* MPLS label stack transfer attribute */ + +#define EA_ID(ea) ((ea) & 0xff) +#define EA_PROTO(ea) ((ea) >> 8) + +typedef uint32_t ip4_addr; + +typedef struct ip6_addr +{ + uint32_t addr[4]; +} ip6_addr; + +typedef ip6_addr ip_addr; + +typedef struct net_addr +{ + uint8_t type; + uint8_t pxlen; + uint16_t length; + uint8_t data[20]; + uint64_t align[0]; +} net_addr; + +typedef struct net_addr_ip4 +{ + uint8_t type; + uint8_t pxlen; + uint16_t length; + ip4_addr prefix; +} net_addr_ip4; + +typedef struct net_addr_ip6 +{ + uint8_t type; + uint8_t pxlen; + uint16_t length; + ip6_addr prefix; +} net_addr_ip6; + +typedef struct net_addr_vpn4 +{ + uint8_t type; + uint8_t pxlen; + uint16_t length; + ip4_addr prefix; + uint64_t rd; +} net_addr_vpn4; + +typedef struct net_addr_vpn6 +{ + uint8_t type; + uint8_t pxlen; + uint16_t length; + ip6_addr prefix; + uint32_t padding; + uint64_t rd; +} net_addr_vpn6; + +typedef struct net_addr_roa4 +{ + uint8_t type; + uint8_t pxlen; + uint16_t length; + ip4_addr prefix; + uint32_t max_pxlen; + uint32_t asn; +} net_addr_roa4; + +typedef struct net_addr_roa6 +{ + uint8_t type; + uint8_t pxlen; + uint16_t length; + ip6_addr prefix; + uint32_t max_pxlen; + uint32_t asn; +} net_addr_roa6; + +typedef struct net_addr_flow4 +{ + uint8_t type; + uint8_t pxlen; + uint16_t length; + ip4_addr prefix; + uint8_t data[0]; +} net_addr_flow4; + +typedef struct net_addr_flow6 +{ + uint8_t type; + uint8_t pxlen; + uint16_t length; + ip6_addr prefix; + uint8_t data[0]; +} net_addr_flow6; + +typedef struct net_addr_mpls +{ + uint8_t type; + uint8_t pxlen; + uint16_t length; + uint32_t label; +} net_addr_mpls; + +typedef struct net_addr_ip6_sadr +{ + uint8_t type; + uint8_t dst_pxlen; + uint16_t length; + ip6_addr dst_prefix; + int32_t src_pxlen; /* s32 to avoid padding */ + ip6_addr src_prefix; +} net_addr_ip6_sadr; + +typedef union net_addr_union +{ + net_addr n; + net_addr_ip4 ip4; + net_addr_ip6 ip6; + net_addr_vpn4 vpn4; + net_addr_vpn6 vpn6; + net_addr_roa4 roa4; + net_addr_roa6 roa6; + net_addr_flow4 flow4; + net_addr_flow6 flow6; + net_addr_ip6_sadr ip6_sadr; + net_addr_mpls mpls; +} net_addr_union; + +void read_bird_feed(const char* sock_name, const char* vrf, class rib_t* rib); + +#endif diff --git a/controlplane/meson.build b/controlplane/meson.build index f888b09c..be78575b 100644 --- a/controlplane/meson.build +++ b/controlplane/meson.build @@ -31,7 +31,8 @@ sources = files('acl_compiler.cpp', 'rib.cpp', 'route.cpp', 'telegraf.cpp', - 'tun64.cpp') + 'tun64.cpp', + 'libbird.cpp') foreach arch : archs bin = 'yanet-controlplane' diff --git a/controlplane/rib.cpp b/controlplane/rib.cpp index 49cdb56e..5f0dbba7 100644 --- a/controlplane/rib.cpp +++ b/controlplane/rib.cpp @@ -1,6 +1,9 @@ #include "rib.h" +#include "libbird.h" #include "controlplane.h" +#include + using namespace controlplane::module; eResult rib_t::init() @@ -873,4 +876,22 @@ void rib_t::rib_thread() std::this_thread::sleep_for(std::chrono::milliseconds{200}); } + +} + +void rib_t::bird_thread() +{ + while (!flagStop) { + read_bird_feed("/tmp/export.sock", "default", this); + + common::icp::rib_update::clear request = {"bgp", std::nullopt}; +/* std::get<1>(request) = {peer_address, + {"default", ///< @todo: vrf + YANET_RIB_PRIORITY_DEFAULT}}; +*/ + rib_clear(request); + + std::this_thread::sleep_for(std::chrono::milliseconds{200}); + } + } diff --git a/controlplane/rib.h b/controlplane/rib.h index 5013421e..d0bc45ca 100644 --- a/controlplane/rib.h +++ b/controlplane/rib.h @@ -46,6 +46,7 @@ class rib_t : public cModule void rib_eor(const common::icp::rib_update::eor& request); void rib_thread(); + void bird_thread(); protected: mutable std::mutex rib_update_mutex; From cf74ab64da87651e58106c1918aefb7eb1208243 Mon Sep 17 00:00:00 2001 From: Georgy Kirichenko Date: Fri, 22 Nov 2024 14:42:02 +0300 Subject: [PATCH 2/6] Start bird import thread --- controlplane/rib.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/controlplane/rib.cpp b/controlplane/rib.cpp index 5f0dbba7..62e9d1a1 100644 --- a/controlplane/rib.cpp +++ b/controlplane/rib.cpp @@ -64,6 +64,10 @@ eResult rib_t::init() rib_thread(); }); + funcThreads.emplace_back([this]() { + bird_thread(); + }); + return eResult::success; } From 08dd8b3f231bb461cd9e3335f7d8906a3c963da3 Mon Sep 17 00:00:00 2001 From: Aleksandr Stepanov Date: Wed, 12 Feb 2025 15:58:44 +0300 Subject: [PATCH 3/6] Added the ability to read route information from multiple bird tables --- controlplane/base.h | 1 + controlplane/configparser.cpp | 30 ++++++++++ controlplane/configparser.h | 1 + controlplane/libbird.cpp | 59 ++++++++++++++++++-- controlplane/libbird.h | 6 +- controlplane/rib.cpp | 102 +++++++++++++++++++++++++++++----- controlplane/rib.h | 20 ++++++- 7 files changed, 196 insertions(+), 23 deletions(-) diff --git a/controlplane/base.h b/controlplane/base.h index 12b4b3d1..4c07114c 100644 --- a/controlplane/base.h +++ b/controlplane/base.h @@ -453,6 +453,7 @@ class base_t rib; common::memory_manager::memory_group root_memory_group; + std::map, int> birds_import; }; // diff --git a/controlplane/configparser.cpp b/controlplane/configparser.cpp index 1ec35a0f..8dac24ea 100644 --- a/controlplane/configparser.cpp +++ b/controlplane/configparser.cpp @@ -135,6 +135,12 @@ controlplane::base_t config_parser_t::loadConfig(const std::string& rootFilePath { loadConfig_memory_group(baseNext.root_memory_group, rootJson["memory_groups"]); } + + if (exist(rootJson, "bird_import")) + { + loadConfig_route_bird(baseNext, + rootJson["bird_import"]); + } } catch (const error_result_t& err) { @@ -197,6 +203,30 @@ controlplane::base_t config_parser_t::loadConfig(const std::string& rootFilePath return baseNext; } +void config_parser_t::loadConfig_route_bird(controlplane::base_t& baseNext, + const nlohmann::json& birdJson) +{ + for (const auto& elemJson : birdJson) + { + + std::string vrf = elemJson.value("vrf", ""); + if (vrf.empty()) + { + throw error_result_t(eResult::invalidConfigurationFile, "invalid configuration file: bird_import: vrf is not set"); + } + + std::string socket = elemJson.value("socket", ""); + if (socket.empty()) + { + throw error_result_t(eResult::invalidConfigurationFile, "invalid configuration file: bird_import: socket is not set"); + } + + int delay = elemJson.value("delay", 0); + + baseNext.birds_import[{vrf, socket}] = delay; + } +} + void config_parser_t::loadConfig_logicalPort(controlplane::base_t& baseNext, const std::string& moduleId, const nlohmann::json& moduleJson) diff --git a/controlplane/configparser.h b/controlplane/configparser.h index 2b86dad8..357b8cd0 100644 --- a/controlplane/configparser.h +++ b/controlplane/configparser.h @@ -21,6 +21,7 @@ class config_parser_t void loadConfig_logicalPort(controlplane::base_t& baseNext, const std::string& moduleId, const nlohmann::json& moduleJson); void loadConfig_route(controlplane::base_t& baseNext, const std::string& moduleId, const nlohmann::json& moduleJson, const std::string& rootFilePath, const std::map& jsons); void loadConfig_route_peers(controlplane::base_t& baseNext, controlplane::route::config_t& route, const nlohmann::json& json, const std::string& rootFilePath, const std::map& jsons); + void loadConfig_route_bird(controlplane::base_t& baseNext, const nlohmann::json& birdJson); void loadConfig_decap(controlplane::base_t& baseNext, const std::string& moduleId, const nlohmann::json& moduleJson); void loadConfig_nat64stateful(controlplane::base_t& baseNext, const std::string& moduleId, const nlohmann::json& moduleJson, const std::string& rootFilePath, const std::map& jsons); void loadConfig_nat64stateless(controlplane::base_t& baseNext, const std::string& moduleId, const nlohmann::json& moduleJson, const std::string& rootFilePath, const std::map& jsons); diff --git a/controlplane/libbird.cpp b/controlplane/libbird.cpp index ecffeec4..64747d68 100644 --- a/controlplane/libbird.cpp +++ b/controlplane/libbird.cpp @@ -3,6 +3,8 @@ #include "common/icp.h" #include "common/type.h" #include "controlplane/rib.h" +#include +#include #include static inline bool @@ -591,8 +593,21 @@ parse_route_update(uintptr_t* ppos, uintptr_t end, const char* vrf, common::icp: return true; } -void read_bird_feed(const char* sock_name, const char* vrf, class rib_t* rib) +/** + * read_bird_feed + * @sock_name: the path to the unix socket through which bird uploads data + * @vrf: the string with the vrf name + * @handler: handler events for receiving data from bird + * @pipe_close: the pipe descriptor through which information about the need to terminate the stream will + * be transmitted, a negative value means that verification should be disabled. + * + * return value: + * - true: some kind of error has occurred and it may be worth running the function after some time interval. + * - false: data was received through pipe indicating that the thread needs to be completed + */ +bool read_bird_feed(const char* sock_name, const char* vrf, rib_update_handler handler, int pipe_close) { + bool result = true; /* Connect to bird export socket. */ int bird_sock = socket(AF_UNIX, SOCK_STREAM, 0); struct sockaddr_un server_addr; @@ -602,7 +617,8 @@ void read_bird_feed(const char* sock_name, const char* vrf, class rib_t* rib) (struct sockaddr*)&server_addr, sizeof(server_addr)) != 0) { - return; + YANET_LOG_ERROR("error connect to socket %s, %d: %s\n", server_addr.sun_path, errno, strerror(errno)); + return result; } /* @@ -613,8 +629,13 @@ void read_bird_feed(const char* sock_name, const char* vrf, class rib_t* rib) void* read_buf = malloc(buf_size); if (read_buf == NULL) { + YANET_LOG_ERROR("error malloc buffer size: %ld", buf_size); + if (pipe_close >= 0) + { + close(pipe_close); + } close(bird_sock); - return; + return result; } /* * The variable is the offset to read data to and denotes the first @@ -631,6 +652,28 @@ void read_bird_feed(const char* sock_name, const char* vrf, class rib_t* rib) for (;;) { + if (pipe_close >= 0) + { + /* Waiting either for receiving data in the socket, or for a signal to close the stream via pipe */ + int max_sd = (pipe_close > bird_sock ? pipe_close : bird_sock); + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(pipe_close, &rfds); + FD_SET(bird_sock, &rfds); + + int retval = select(max_sd + 1, &rfds, NULL, NULL, NULL); + if (retval < 0) + { + YANET_LOG_ERROR("!!!!! error select %d: %s\n", errno, strerror(errno)); + } + else if (FD_ISSET(pipe_close, &rfds)) + { + result = false; + YANET_LOG_INFO("closing thread for reading the vrf=%s from the bird socket: %s\n", vrf, sock_name); + break; + } + } + /* Read as mush as possible data */ ssize_t readen = read(bird_sock, (void*)((uintptr_t)read_buf + read_pos), @@ -662,8 +705,8 @@ void read_bird_feed(const char* sock_name, const char* vrf, class rib_t* rib) parse_pos = pos - (uintptr_t)read_buf; } - rib->rib_update(requests); - rib->rib_flush(); + handler(requests); + requests.clear(); if (buf_size - read_pos < buf_size / 2) { @@ -675,8 +718,12 @@ void read_bird_feed(const char* sock_name, const char* vrf, class rib_t* rib) } } + if (pipe_close >= 0) + { + close(pipe_close); + } free(read_buf); close(bird_sock); - return; + return result; } diff --git a/controlplane/libbird.h b/controlplane/libbird.h index c10ba54e..307ecb29 100644 --- a/controlplane/libbird.h +++ b/controlplane/libbird.h @@ -1,6 +1,8 @@ #ifndef LIBBIRD_H #define LIBBIRD_H +#include "common/icp.h" +#include #include #define NET_IP4 1 @@ -182,6 +184,8 @@ typedef union net_addr_union net_addr_mpls mpls; } net_addr_union; -void read_bird_feed(const char* sock_name, const char* vrf, class rib_t* rib); +using rib_update_handler = std::function; + +bool read_bird_feed(const char* sock_name, const char* vrf, rib_update_handler handler, int pipe_close = -1); #endif diff --git a/controlplane/rib.cpp b/controlplane/rib.cpp index 62e9d1a1..17e341e3 100644 --- a/controlplane/rib.cpp +++ b/controlplane/rib.cpp @@ -1,6 +1,6 @@ #include "rib.h" -#include "libbird.h" #include "controlplane.h" +#include "libbird.h" #include @@ -64,13 +64,14 @@ eResult rib_t::init() rib_thread(); }); - funcThreads.emplace_back([this]() { - bird_thread(); - }); - return eResult::success; } +void rib_t::stop() +{ + bird_readers.TryStopAllReaders(); +} + void rib_t::reload([[maybe_unused]] const controlplane::base_t& base_prev, const controlplane::base_t& base_next, [[maybe_unused]] common::idp::updateGlobalBase::request& globalbase) @@ -122,6 +123,8 @@ void rib_t::reload([[maybe_unused]] const controlplane::base_t& base_prev, } rib_update(request); + + reload_bird_threads(base_prev, base_next); } void rib_t::rib_update(const common::icp::rib_update::request& request) @@ -880,22 +883,91 @@ void rib_t::rib_thread() std::this_thread::sleep_for(std::chrono::milliseconds{200}); } +} + +void rib_t::reload_bird_threads(const controlplane::base_t& base_prev, const controlplane::base_t& base_next) +{ + for (const auto& [key, delay] : base_next.birds_import) + { + if (base_prev.birds_import.find(key) == base_prev.birds_import.end()) + { + const auto& [vrf_name, socket_path] = key; + bird_readers.StartReader(vrf_name, socket_path, delay, [this](common::icp::rib_update::request& request) { + rib_update(request); + rib_flush(); + request.clear(); + }); + } + } + for (const auto& iter : base_prev.birds_import) + { + if (base_next.birds_import.find(iter.first) == base_next.birds_import.end()) + { + const auto& [vrf_name, socket_path] = iter.first; + bird_readers.TryStopReader(vrf_name, socket_path); + } + } } -void rib_t::bird_thread() +void BirdReaders::StartReader(const std::string& vrf_name, const std::string& socket_path, int delay, rib_update_handler handler) { - while (!flagStop) { - read_bird_feed("/tmp/export.sock", "default", this); + YANET_LOG_INFO("run a thread to read the vrf=%s from the bird socket: %s\n", vrf_name.c_str(), socket_path.c_str()); + int pipes[2]; + if (pipe(pipes) < 0) + { + YANET_LOG_ERROR("error create pipes %d: %s\n", errno, strerror(errno)); + return; + } + thread_pipes[{vrf_name, socket_path}] = pipes[1]; + bird_threads.emplace_back([vrf_name, socket_path, delay, handler, pipes]() { + if (delay > 0) + { + sleep(delay); + } + while (true) + { + common::icp::rib_update::request request; + if (!read_bird_feed(socket_path.c_str(), vrf_name.c_str(), handler, pipes[0])) + { + break; + } + std::this_thread::sleep_for(std::chrono::milliseconds{200}); + } + }); +} - common::icp::rib_update::clear request = {"bgp", std::nullopt}; -/* std::get<1>(request) = {peer_address, - {"default", ///< @todo: vrf - YANET_RIB_PRIORITY_DEFAULT}}; -*/ - rib_clear(request); +void BirdReaders::TryStopReader(const std::string& vrf_name, const std::string& socket_path) +{ + auto iter = thread_pipes.find({vrf_name, socket_path}); + if (iter == thread_pipes.end()) + { + YANET_LOG_ERROR("try stopping a thread to read the vrf=%s from the bird socket: %s, but thread not found\n", + vrf_name.c_str(), + socket_path.c_str()); + } + else + { + YANET_LOG_INFO("try stopping a thread to read the vrf=%s from the bird socket: %s\n", + vrf_name.c_str(), + socket_path.c_str()); + close(iter->second); + thread_pipes.erase(iter); + } +} - std::this_thread::sleep_for(std::chrono::milliseconds{200}); +void BirdReaders::TryStopAllReaders() +{ + for (auto& iter : thread_pipes) + { + close(iter.second); } + for (auto& thread : bird_threads) + { + if (thread.joinable()) + { + thread.join(); + } + } } diff --git a/controlplane/rib.h b/controlplane/rib.h index d0bc45ca..0855026b 100644 --- a/controlplane/rib.h +++ b/controlplane/rib.h @@ -1,5 +1,6 @@ #pragma once +#include "libbird.h" #include "module.h" #include #include @@ -19,6 +20,20 @@ using path_info_to_nexthop_stuff_ptr_t = common::rib::path_info_to_nexthop_stuff } +class BirdReaders +{ +public: + void StartReader(const std::string& vrf_name, const std::string& socket_path, int delay, rib_update_handler handler); + void TryStopReader(const std::string& vrf_name, const std::string& socket_path); + void TryStopAllReaders(); + +private: + using bird_reader_key = std::pair; // vrf + socket_path + + std::vector bird_threads; + std::map thread_pipes; +}; + class rib_t : public cModule { public: @@ -26,6 +41,7 @@ class rib_t : public cModule ~rib_t() override = default; eResult init() override; + void stop() override; void reload(const controlplane::base_t& base_prev, const controlplane::base_t& base_next, common::idp::updateGlobalBase::request& globalbase) override; void rib_update(const common::icp::rib_update::request& request); @@ -46,7 +62,7 @@ class rib_t : public cModule void rib_eor(const common::icp::rib_update::eor& request); void rib_thread(); - void bird_thread(); + void reload_bird_threads(const controlplane::base_t& base_prev, const controlplane::base_t& base_next); protected: mutable std::mutex rib_update_mutex; @@ -81,4 +97,6 @@ class rib_t : public cModule common::uint64, ///< paths common::uint8>> ///< eor summary; + + BirdReaders bird_readers; }; From 32beb697ab57de120448243b6f40f289ebdcfb5e Mon Sep 17 00:00:00 2001 From: Aleksandr Stepanov Date: Tue, 11 Feb 2025 17:27:58 +0300 Subject: [PATCH 4/6] Added emulation of working with bird to autotests --- autotest/autotest.cpp | 119 ++++++++++++++++++++++++++++++++++++++++++ autotest/autotest.h | 5 ++ 2 files changed, 124 insertions(+) diff --git a/autotest/autotest.cpp b/autotest/autotest.cpp index 5e6a8950..ec280c20 100644 --- a/autotest/autotest.cpp +++ b/autotest/autotest.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -1481,6 +1482,12 @@ void tAutotest::mainThread() result = step_dumpPackets(yamlStep["dumpPackets"], configFilePath); } + else if (yamlStep["bird"]) + { + YANET_LOG_DEBUG("step: bird\n"); + + result = step_bird(yamlStep["bird"], configFilePath); + } else { YANET_LOG_ERROR("unknown step\n"); @@ -1505,6 +1512,8 @@ void tAutotest::mainThread() std::abort(); } + stopBirdThreads(); + YANET_LOG_PRINT(ANSI_COLOR_GREEN "done '%s'\n\n" ANSI_COLOR_RESET, configFilePath.data()); fflush(stdout); fflush(stderr); @@ -2000,6 +2009,116 @@ bool tAutotest::step_dumpPackets(const YAML::Node& yamlStep, return true; } +void copyAllFromFileToSocket(int fd_file, int fd_socket) +{ + struct sockaddr_in6 address; + socklen_t addressLength = sizeof(address); + memset((char*)&address, 0, sizeof(address)); + int clientSocket = accept(fd_socket, (struct sockaddr*)&address, &addressLength); + if (clientSocket < 0) + { + YANET_LOG_ERROR("error on accept socket %d: %s\n", errno, strerror(errno)); + return; + } + + uint8_t buffer[64]; + ssize_t was_read; + while (was_read = read(fd_file, buffer, sizeof(buffer)), was_read > 0) + { + ssize_t totalSend = 0; + while (totalSend < was_read) + { + int ret = send(clientSocket, buffer + totalSend, was_read - totalSend, MSG_NOSIGNAL); + if (ret <= 0) + { + YANET_LOG_ERROR("error send %d: %s\n", errno, strerror(errno)); + return; + } + totalSend += ret; + } + } + + if (was_read < 0) + { + YANET_LOG_ERROR("error read %d: %s\n", errno, strerror(errno)); + } + else + { + YANET_LOG_DEBUG("stopping thread copy from bird data\n"); + } +} + +bool tAutotest::step_bird(const YAML::Node& yamlStep, + const std::string& path) +{ + for (const auto& yamlBird : yamlStep) + { + std::string filename_data = path + "/" + yamlBird["data"].as(); + std::string filename_sock = yamlBird["sock"].as(); + YANET_LOG_DEBUG("run bird on file data: %s, socket: %s\n", filename_data.c_str(), filename_sock.c_str()); + + // Open file + int fd_file = open(filename_data.c_str(), O_RDONLY); + if (fd_file < 0) + { + YANET_LOG_ERROR("error '%d: %s' open file: %s\n", fd_file, strerror(fd_file), filename_data.c_str()); + return false; + } + birdDescriptors.push_back(fd_file); + + // Prepare socket + int fd_socket = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd_socket < 0) + { + YANET_LOG_ERROR("error create socket %d: %s\n", fd_socket, strerror(fd_socket)); + return false; + } + + sockaddr_un address; + memset((char*)&address, 0, sizeof(address)); + address.sun_family = AF_UNIX; + strncpy(address.sun_path, filename_sock.c_str(), sizeof(address.sun_path) - 1); + address.sun_path[sizeof(address.sun_path) - 1] = 0; + + unlink(filename_sock.c_str()); + + if (bind(fd_socket, (struct sockaddr*)&address, sizeof(address)) < 0) + { + YANET_LOG_ERROR("error bind socket on: %s\n", filename_sock.c_str()); + return false; + } + + if (listen(fd_socket, 64) < 0) + { + YANET_LOG_ERROR("error listen socket on: %s\n", filename_sock.c_str()); + return false; + } + birdDescriptors.push_back(fd_socket); + + birdThreads.emplace_back([fd_file, fd_socket]() { + copyAllFromFileToSocket(fd_file, fd_socket); + }); + } + + return true; +} + +void tAutotest::stopBirdThreads() +{ + for (int fd : birdDescriptors) + { + close(fd); + } + + for (auto& thread : birdThreads) + { + if (thread.joinable()) + { + thread.join(); + } + } +} + void tAutotest::fflushSharedMemory() { size_t size = std::get<0>(rawShmInfo); diff --git a/autotest/autotest.h b/autotest/autotest.h index 1725a16c..0a723adb 100644 --- a/autotest/autotest.h +++ b/autotest/autotest.h @@ -70,10 +70,12 @@ class tAutotest bool step_reload_async(const YAML::Node& yamlStep); bool step_echo(const YAML::Node& yamlStep); bool step_dumpPackets(const YAML::Node& yamlStep, const std::string& path); + bool step_bird(const YAML::Node& yamlStep, const std::string& path); eResult initSockets(); eResult initSharedMemory(); void fflushSharedMemory(); + void stopBirdThreads(); bool step_memorize_counter_value(const YAML::Node& yamlStep); bool step_diff_with_kept_counter_value(const YAML::Node& yamlStep); @@ -129,6 +131,9 @@ class tAutotest std::string memorized_counter_name; uint32_t memorized_coreId; uint64_t memorized_counter_value; + + std::vector birdThreads; + std::vector birdDescriptors; }; bool readTimeLimited(int fd, u_char* data, ssize_t len, std::chrono::system_clock::time_point time_to_give_up); From 3c3eccecb6ffc01d8980ac4e3f6bce7adfc56935 Mon Sep 17 00:00:00 2001 From: Aleksandr Stepanov Date: Tue, 11 Feb 2025 17:36:42 +0300 Subject: [PATCH 5/6] Added autotests that emulate reading the routing table from bird --- .../autotest.yaml | 2 + .../082_bird_import/001-expect.pcap | Bin 0 -> 600 bytes .../082_bird_import/001-send.pcap | Bin 0 -> 600 bytes .../082_bird_import/002-expect.pcap | Bin 0 -> 600 bytes .../082_bird_import/003-expect.pcap | Bin 0 -> 24 bytes .../082_bird_import/autotest.yaml | 49 ++++++++++ .../082_bird_import/bird-master4_1.dat | Bin 0 -> 276 bytes .../082_bird_import/bird-master4_2.dat | Bin 0 -> 184 bytes .../082_bird_import/bird-master4_3.dat | Bin 0 -> 276 bytes .../082_bird_import/bird-master6_1.dat | Bin 0 -> 276 bytes .../082_bird_import/bird-master6_2.dat | Bin 0 -> 184 bytes .../082_bird_import/bird-master6_3.dat | Bin 0 -> 276 bytes .../082_bird_import/controlplane.conf | 70 ++++++++++++++ .../082_bird_import/controlplane2.conf | 70 ++++++++++++++ .../082_bird_import/controlplane3.conf | 70 ++++++++++++++ .../units/001_one_port/082_bird_import/gen.py | 89 ++++++++++++++++++ 16 files changed, 350 insertions(+) create mode 100644 autotest/units/001_one_port/082_bird_import/001-expect.pcap create mode 100644 autotest/units/001_one_port/082_bird_import/001-send.pcap create mode 100644 autotest/units/001_one_port/082_bird_import/002-expect.pcap create mode 100644 autotest/units/001_one_port/082_bird_import/003-expect.pcap create mode 100644 autotest/units/001_one_port/082_bird_import/autotest.yaml create mode 100644 autotest/units/001_one_port/082_bird_import/bird-master4_1.dat create mode 100644 autotest/units/001_one_port/082_bird_import/bird-master4_2.dat create mode 100644 autotest/units/001_one_port/082_bird_import/bird-master4_3.dat create mode 100644 autotest/units/001_one_port/082_bird_import/bird-master6_1.dat create mode 100644 autotest/units/001_one_port/082_bird_import/bird-master6_2.dat create mode 100644 autotest/units/001_one_port/082_bird_import/bird-master6_3.dat create mode 100644 autotest/units/001_one_port/082_bird_import/controlplane.conf create mode 100644 autotest/units/001_one_port/082_bird_import/controlplane2.conf create mode 100644 autotest/units/001_one_port/082_bird_import/controlplane3.conf create mode 100755 autotest/units/001_one_port/082_bird_import/gen.py diff --git a/autotest/units/001_one_port/076_rfc_5549_route_tunnel_random_source/autotest.yaml b/autotest/units/001_one_port/076_rfc_5549_route_tunnel_random_source/autotest.yaml index 42d558b3..b75afe01 100644 --- a/autotest/units/001_one_port/076_rfc_5549_route_tunnel_random_source/autotest.yaml +++ b/autotest/units/001_one_port/076_rfc_5549_route_tunnel_random_source/autotest.yaml @@ -25,3 +25,5 @@ steps: - port: kni0 send: 002-send.pcap expect: 002-expect.pcap +- cli: + - rib static remove default 1.0.0.0/24 4444::1 1100 10000 diff --git a/autotest/units/001_one_port/082_bird_import/001-expect.pcap b/autotest/units/001_one_port/082_bird_import/001-expect.pcap new file mode 100644 index 0000000000000000000000000000000000000000..0ac5f9cbb5f57a4e8d5687a41b24dc949b3f91ba GIT binary patch literal 600 zcmca|c+)~A1{MYw`2U}Qff2?5(t1z~q>PP?83dJ#T|yff7Flwi6UP9W>f1?y(UqT31R z2@nPX6o0qfO#q2;SlcrQf*5eXh!SkT@IVg>3QYu>kHpn~6-jbHKWpvFLUJdIE&e{oQsq0VK*{?Z6-iV!#0- fO0WUL13fG#G!bY%6-)#M6cV6@iNL@^0&o)n0BJc4 literal 0 HcmV?d00001 diff --git a/autotest/units/001_one_port/082_bird_import/002-expect.pcap b/autotest/units/001_one_port/082_bird_import/002-expect.pcap new file mode 100644 index 0000000000000000000000000000000000000000..68d86d8e162f0a6049d2219820bb5bb81ceea17c GIT binary patch literal 600 zcmca|c+)~A1{MYw`2U}Qff2?5(t1z~q+DEF7zCA!T|yff7$g=iW_5*vnJ5G!2Uwnw5k(ZjkpNQ6EDQ_+NOJ%G1I^^(;)(!@ j!SpV`Z#EMtW;3Ch&4gq%n5ocK1NH}_0O{^#2AT~3-8vDO literal 0 HcmV?d00001 diff --git a/autotest/units/001_one_port/082_bird_import/bird-master4_2.dat b/autotest/units/001_one_port/082_bird_import/bird-master4_2.dat new file mode 100644 index 0000000000000000000000000000000000000000..abe1a9f9db58a43f262f0dc6f6cda5d013383b44 GIT binary patch literal 184 zcma!GU|?Vr-~f^gj35FZz@|U~sDYVg=iW_5*vnJ5G!2Uwnw2}KmbkpNQ6EDQ_+NOJ%G1I^^(;)(!@ g!So_YqcEZJOr)621T$Lz;w}^ev2aK+n;B>}0QY+fg8%>k literal 0 HcmV?d00001 diff --git a/autotest/units/001_one_port/082_bird_import/bird-master6_1.dat b/autotest/units/001_one_port/082_bird_import/bird-master6_1.dat new file mode 100644 index 0000000000000000000000000000000000000000..08052b2e34a09a4f152d05d1f76f22ba6dc283b8 GIT binary patch literal 276 zcma!GU|?Wk5CM{yK;UB2tgcXmI3t1yXGs8A%q$EH0$>pc31WiOMgTFB1ITPlogNgJ V&4gk$h=(6w+Rng$%iYXCqW~V92!sFt literal 0 HcmV?d00001 diff --git a/autotest/units/001_one_port/082_bird_import/bird-master6_2.dat b/autotest/units/001_one_port/082_bird_import/bird-master6_2.dat new file mode 100644 index 0000000000000000000000000000000000000000..adf0a171e4f7d3f89f410843632b429668135ea8 GIT binary patch literal 184 zcma!GU|?XX5CM{mK#U(?Qy~G=!py?JAOO}1Awf)LAdUcHCI^t&OhAkZ7F=kW)fI{m L!&Je*fX{3I*eV5z literal 0 HcmV?d00001 diff --git a/autotest/units/001_one_port/082_bird_import/bird-master6_3.dat b/autotest/units/001_one_port/082_bird_import/bird-master6_3.dat new file mode 100644 index 0000000000000000000000000000000000000000..5025fc707b08b69cba2b53177e572f092716b437 GIT binary patch literal 276 zcma!GU|?Wk5CM{yK;UB2tgcXmI1_>iXGs8A%q$EH0$>pc31Tt=aRd-EIe^Rt@$dsu R%m%3in_WSDvzdWr0{~ha1*!l5 literal 0 HcmV?d00001 diff --git a/autotest/units/001_one_port/082_bird_import/controlplane.conf b/autotest/units/001_one_port/082_bird_import/controlplane.conf new file mode 100644 index 00000000..88b91d8e --- /dev/null +++ b/autotest/units/001_one_port/082_bird_import/controlplane.conf @@ -0,0 +1,70 @@ +{ + "modules": { + "lp0.100": { + "type": "logicalPort", + "physicalPort": "kni0", + "vlanId": "100", + "macAddress": "00:11:22:33:44:55", + "nextModule": "route0" + }, + "lp0.200": { + "type": "logicalPort", + "physicalPort": "kni0", + "vlanId": "200", + "macAddress": "00:11:22:33:44:55", + "nextModule": "route0" + }, + "lp0.300": { + "type": "logicalPort", + "physicalPort": "kni0", + "vlanId": "300", + "macAddress": "00:11:22:33:44:55", + "nextModule": "route0" + }, + "lp0.400": { + "type": "logicalPort", + "physicalPort": "kni0", + "vlanId": "400", + "macAddress": "00:11:22:33:44:55", + "nextModule": "route0" + }, + "route0": { + "type": "route", + "interfaces": { + "kni0.100": { + "nextModule": "lp0.100" + }, + "kni0.200": { + "neighborIPv4Address": "10.10.10.1", + "neighborIPv6Address": "10::1", + "neighborMacAddress": "00:00:00:22:22:22", + "nextModule": "lp0.200" + }, + "kni0.300": { + "neighborIPv4Address": "10.10.10.2", + "neighborIPv6Address": "10::2", + "neighborMacAddress": "00:00:00:33:33:33", + "nextModule": "lp0.300" + }, + "kni0.400": { + "neighborIPv4Address": "10.10.10.3", + "neighborIPv6Address": "10::3", + "neighborMacAddress": "00:00:00:44:44:44", + "nextModule": "lp0.400" + } + } + } + }, + "bird_import": [ + { + "socket": "/tmp/yanet-bird-master4_1.sock", + "vrf": "default", + "delay": 2 + }, + { + "socket": "/tmp/yanet-bird-master6_1.sock", + "vrf": "default", + "delay": 2 + } + ] +} diff --git a/autotest/units/001_one_port/082_bird_import/controlplane2.conf b/autotest/units/001_one_port/082_bird_import/controlplane2.conf new file mode 100644 index 00000000..8821d8f9 --- /dev/null +++ b/autotest/units/001_one_port/082_bird_import/controlplane2.conf @@ -0,0 +1,70 @@ +{ + "modules": { + "lp0.100": { + "type": "logicalPort", + "physicalPort": "kni0", + "vlanId": "100", + "macAddress": "00:11:22:33:44:55", + "nextModule": "route0" + }, + "lp0.200": { + "type": "logicalPort", + "physicalPort": "kni0", + "vlanId": "200", + "macAddress": "00:11:22:33:44:55", + "nextModule": "route0" + }, + "lp0.300": { + "type": "logicalPort", + "physicalPort": "kni0", + "vlanId": "300", + "macAddress": "00:11:22:33:44:55", + "nextModule": "route0" + }, + "lp0.400": { + "type": "logicalPort", + "physicalPort": "kni0", + "vlanId": "400", + "macAddress": "00:11:22:33:44:55", + "nextModule": "route0" + }, + "route0": { + "type": "route", + "interfaces": { + "kni0.100": { + "nextModule": "lp0.100" + }, + "kni0.200": { + "neighborIPv4Address": "10.10.10.1", + "neighborIPv6Address": "10::1", + "neighborMacAddress": "00:00:00:22:22:22", + "nextModule": "lp0.200" + }, + "kni0.300": { + "neighborIPv4Address": "10.10.10.2", + "neighborIPv6Address": "10::2", + "neighborMacAddress": "00:00:00:33:33:33", + "nextModule": "lp0.300" + }, + "kni0.400": { + "neighborIPv4Address": "10.10.10.3", + "neighborIPv6Address": "10::3", + "neighborMacAddress": "00:00:00:44:44:44", + "nextModule": "lp0.400" + } + } + } + }, + "bird_import": [ + { + "socket": "/tmp/yanet-bird-master4_2.sock", + "vrf": "default", + "delay": 2 + }, + { + "socket": "/tmp/yanet-bird-master6_2.sock", + "vrf": "default", + "delay": 2 + } + ] +} diff --git a/autotest/units/001_one_port/082_bird_import/controlplane3.conf b/autotest/units/001_one_port/082_bird_import/controlplane3.conf new file mode 100644 index 00000000..f9c592b1 --- /dev/null +++ b/autotest/units/001_one_port/082_bird_import/controlplane3.conf @@ -0,0 +1,70 @@ +{ + "modules": { + "lp0.100": { + "type": "logicalPort", + "physicalPort": "kni0", + "vlanId": "100", + "macAddress": "00:11:22:33:44:55", + "nextModule": "route0" + }, + "lp0.200": { + "type": "logicalPort", + "physicalPort": "kni0", + "vlanId": "200", + "macAddress": "00:11:22:33:44:55", + "nextModule": "route0" + }, + "lp0.300": { + "type": "logicalPort", + "physicalPort": "kni0", + "vlanId": "300", + "macAddress": "00:11:22:33:44:55", + "nextModule": "route0" + }, + "lp0.400": { + "type": "logicalPort", + "physicalPort": "kni0", + "vlanId": "400", + "macAddress": "00:11:22:33:44:55", + "nextModule": "route0" + }, + "route0": { + "type": "route", + "interfaces": { + "kni0.100": { + "nextModule": "lp0.100" + }, + "kni0.200": { + "neighborIPv4Address": "10.10.10.1", + "neighborIPv6Address": "10::1", + "neighborMacAddress": "00:00:00:22:22:22", + "nextModule": "lp0.200" + }, + "kni0.300": { + "neighborIPv4Address": "10.10.10.2", + "neighborIPv6Address": "10::2", + "neighborMacAddress": "00:00:00:33:33:33", + "nextModule": "lp0.300" + }, + "kni0.400": { + "neighborIPv4Address": "10.10.10.3", + "neighborIPv6Address": "10::3", + "neighborMacAddress": "00:00:00:44:44:44", + "nextModule": "lp0.400" + } + } + } + }, + "bird_import": [ + { + "socket": "/tmp/yanet-bird-master4_3.sock", + "vrf": "default", + "delay": 2 + }, + { + "socket": "/tmp/yanet-bird-master6_3.sock", + "vrf": "default", + "delay": 2 + } + ] +} diff --git a/autotest/units/001_one_port/082_bird_import/gen.py b/autotest/units/001_one_port/082_bird_import/gen.py new file mode 100755 index 00000000..dedeab96 --- /dev/null +++ b/autotest/units/001_one_port/082_bird_import/gen.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from scapy.all import * +from scapy.contrib.mpls import MPLS + + +def write_pcap(filename, *packetsList): + if len(packetsList) == 0: + writer=PcapWriter(filename) + return + + PcapWriter(filename) + + for packets in packetsList: + if type(packets) == list: + for packet in packets: + packet.time = 0 + wrpcap(filename, [p for p in packet], append=True) + else: + packets.time = 0 + wrpcap(filename, [p for p in packets], append=True) + + +''' +At the first stage, the routing table should be as follows: + route 0.0.0.0/0 via 10.10.10.1; + route 1.0.0.0/8 via 10.10.10.2; + route 2.0.0.0/8 via 10.10.10.3; + route ::/0 via 10::1; + route 1::/64 via 10::2; + route 2::/64 via 10::3; +''' + +write_pcap("001-send.pcap", + Ether(dst="00:11:22:33:44:55", src="00:00:00:00:00:01")/Dot1Q(vlan=100)/IP(dst="1.0.0.127", src="11.0.0.1")/ICMP(), + Ether(dst="00:11:22:33:44:55", src="00:00:00:00:00:01")/Dot1Q(vlan=100)/IP(dst="1.1.0.127", src="11.0.0.1")/ICMP(), + Ether(dst="00:11:22:33:44:55", src="00:00:00:00:00:01")/Dot1Q(vlan=100)/IP(dst="2.0.0.127", src="11.0.0.1")/ICMP(), + Ether(dst="00:11:22:33:44:55", src="00:00:00:00:00:01")/Dot1Q(vlan=100)/IP(dst="3.0.0.127", src="11.0.0.1")/ICMP(), + + Ether(dst="00:11:22:33:44:55", src="00:00:00:00:00:01")/Dot1Q(vlan=100)/IPv6(dst="1::1", src="11::1")/ICMP(), + Ether(dst="00:11:22:33:44:55", src="00:00:00:00:00:01")/Dot1Q(vlan=100)/IPv6(dst="1::1:1", src="11::1")/ICMP(), + Ether(dst="00:11:22:33:44:55", src="00:00:00:00:00:01")/Dot1Q(vlan=100)/IPv6(dst="2::1", src="11::1")/ICMP(), + Ether(dst="00:11:22:33:44:55", src="00:00:00:00:00:01")/Dot1Q(vlan=100)/IPv6(dst="3::1", src="11::1")/ICMP(), +) + +write_pcap("001-expect.pcap", + Ether(dst="00:00:00:33:33:33", src="00:11:22:33:44:55")/Dot1Q(vlan=300)/IP(dst="1.0.0.127", src="11.0.0.1", ttl=63)/ICMP(), + Ether(dst="00:00:00:33:33:33", src="00:11:22:33:44:55")/Dot1Q(vlan=300)/IP(dst="1.1.0.127", src="11.0.0.1", ttl=63)/ICMP(), + Ether(dst="00:00:00:44:44:44", src="00:11:22:33:44:55")/Dot1Q(vlan=400)/IP(dst="2.0.0.127", src="11.0.0.1", ttl=63)/ICMP(), + Ether(dst="00:00:00:22:22:22", src="00:11:22:33:44:55")/Dot1Q(vlan=200)/IP(dst="3.0.0.127", src="11.0.0.1", ttl=63)/ICMP(), + + Ether(dst="00:00:00:33:33:33", src="00:11:22:33:44:55")/Dot1Q(vlan=300)/IPv6(dst="1::1", src="11::1", hlim=63)/ICMP(), + Ether(dst="00:00:00:33:33:33", src="00:11:22:33:44:55")/Dot1Q(vlan=300)/IPv6(dst="1::1:1", src="11::1", hlim=63)/ICMP(), + Ether(dst="00:00:00:44:44:44", src="00:11:22:33:44:55")/Dot1Q(vlan=400)/IPv6(dst="2::1", src="11::1", hlim=63)/ICMP(), + Ether(dst="00:00:00:22:22:22", src="00:11:22:33:44:55")/Dot1Q(vlan=200)/IPv6(dst="3::1", src="11::1", hlim=63)/ICMP(), +) + +''' +At the second stage, the following changes occur: + del route 2.0.0.0/8 via 10.10.10.3; + add route 1.0.0.0/16 via 10.10.10.3; + +The routing table should be as follows: + route 0.0.0.0/0 via 10.10.10.1; + route 1.0.0.0/8 via 10.10.10.2; + route 1.0.0.0/16 via 10.10.10.3; + route ::/0 via 10::1; + route 1::/64 via 10::2; + route 1::/120 via 10::3; + +''' + + +write_pcap("002-expect.pcap", + Ether(dst="00:00:00:44:44:44", src="00:11:22:33:44:55")/Dot1Q(vlan=400)/IP(dst="1.0.0.127", src="11.0.0.1", ttl=63)/ICMP(), + Ether(dst="00:00:00:33:33:33", src="00:11:22:33:44:55")/Dot1Q(vlan=300)/IP(dst="1.1.0.127", src="11.0.0.1", ttl=63)/ICMP(), + Ether(dst="00:00:00:22:22:22", src="00:11:22:33:44:55")/Dot1Q(vlan=200)/IP(dst="2.0.0.127", src="11.0.0.1", ttl=63)/ICMP(), + Ether(dst="00:00:00:22:22:22", src="00:11:22:33:44:55")/Dot1Q(vlan=200)/IP(dst="3.0.0.127", src="11.0.0.1", ttl=63)/ICMP(), + + Ether(dst="00:00:00:44:44:44", src="00:11:22:33:44:55")/Dot1Q(vlan=400)/IPv6(dst="1::1", src="11::1", hlim=63)/ICMP(), + Ether(dst="00:00:00:33:33:33", src="00:11:22:33:44:55")/Dot1Q(vlan=300)/IPv6(dst="1::1:1", src="11::1", hlim=63)/ICMP(), + Ether(dst="00:00:00:22:22:22", src="00:11:22:33:44:55")/Dot1Q(vlan=200)/IPv6(dst="2::1", src="11::1", hlim=63)/ICMP(), + Ether(dst="00:00:00:22:22:22", src="00:11:22:33:44:55")/Dot1Q(vlan=200)/IPv6(dst="3::1", src="11::1", hlim=63)/ICMP(), +) + +''' +At the third stage, all routes are deleted and the routing table should be empty. +''' From b12ac9a5d983d7d7c9548e94c9a0201e2de0a246 Mon Sep 17 00:00:00 2001 From: Aleksandr Stepanov Date: Tue, 11 Feb 2025 17:43:13 +0300 Subject: [PATCH 6/6] Added an application for testing the reading of route information from bird --- demo/bird_app/Makefile | 17 +++++ demo/bird_app/bird_app.cpp | 134 +++++++++++++++++++++++++++++++++ demo/bird_app/build/.gitignore | 1 + 3 files changed, 152 insertions(+) create mode 100644 demo/bird_app/Makefile create mode 100644 demo/bird_app/bird_app.cpp create mode 100644 demo/bird_app/build/.gitignore diff --git a/demo/bird_app/Makefile b/demo/bird_app/Makefile new file mode 100644 index 00000000..39e7bb60 --- /dev/null +++ b/demo/bird_app/Makefile @@ -0,0 +1,17 @@ +CXX := g++ +CXX_FLAGS := -Wall -Wextra -std=c++17 + +BIN := build +SRC := bird_app.cpp ../../controlplane/libbird.cpp +INCLUDE := -I . -I ../.. -I ../../subprojects/json/include/ +LIB := lib +EXECUTABLE := bird_app + + +all: $(BIN)/$(EXECUTABLE) + +$(BIN)/$(EXECUTABLE): $(SRC) + $(CXX) $(CXX_FLAGS) $(INCLUDE) -L$(LIB) $^ -o $@ + +clean: + -rm $(BIN)/* diff --git a/demo/bird_app/bird_app.cpp b/demo/bird_app/bird_app.cpp new file mode 100644 index 00000000..5ef08d9e --- /dev/null +++ b/demo/bird_app/bird_app.cpp @@ -0,0 +1,134 @@ +// #include "bird_app.h" +#include "common/define.h" +#include "controlplane/libbird.h" + +#include +#include + +common::log::LogPriority common::log::logPriority = common::log::TLOG_DEBUG; + +std::ostream& operator<<(std::ostream& os, const common::community_t& lc) +{ + os << lc.toString(); + return os; +} + +std::ostream& operator<<(std::ostream& os, const common::large_community_t& lc) +{ + os << lc.toString(); + return os; +} + +template +std::ostream& PrintCollection(std::ostream& os, T& data, char left, char right) +{ + if (data.empty()) + { + os << "null"; + return os; + } + + os << left; + bool first = true; + for (const auto& value : data) + { + if (first) + { + first = false; + } + else + { + os << ", "; + } + os << value; + } + os << right; + + return os; +} + +template +std::ostream& operator<<(std::ostream& os, const std::set& data) +{ + return PrintCollection(os, data, '{', '}'); +} + +template +std::ostream& operator<<(std::ostream& os, const std::vector& data) +{ + return PrintCollection(os, data, '[', ']'); +} + +void PrintRibRequests(common::icp::rib_update::request& requests) +{ + std::cout << "--------------------------------------------\n"; + std::cout << "new requests: " << requests.size() << "\n"; + for (const auto& action : requests) + { + if (std::holds_alternative(action)) + { + const auto& [protocol, vrf, priority, insert_data] = std::get(action); + std::cout << "insert, protocol=" << protocol << ", vrf=" << vrf << ", priority=" << priority << "\n"; + for (const auto& [key, tables] : insert_data) + { + const auto& [peer, origin, med, aspath, community, large_community, local_preference] = key; + std::cout << "\tpeer=" << peer.toString() << ", origin=" << origin << ", med=" << med + << ", aspath=" << aspath << ", community=" << community + << ", large_community=" << large_community << "\n"; + for (const auto& [table_name, nexthops] : tables) + { + std::cout << "\t\ttable_name=" << table_name << "\n"; + for (const auto& [nexthop, prefixes] : nexthops) + { + std::cout << "\t\t\tnexthop=" << nexthop.toString() << "\n"; + for (const auto& [prefix, path_information, labels] : prefixes) + { + std::cout << "\t\t\t\tprefix=" << prefix.toString() << ", path_information=" + << path_information << ", labels=" << labels << "\n"; + } + } + } + } + } + else if (std::holds_alternative(action)) + { + const auto& [protocol, vrf, priority, peers] = std::get(action); + std::cout << "remove, protocol=" << protocol << ", vrf=" << vrf << ", priority=" << priority << "\n"; + for (const auto& [peer, tables] : peers) + { + std::cout << "\tpeer=" << peer.toString() << "\n"; + for (const auto& [table_name, prefixes] : tables) + { + std::cout << "\t\ttable_name=" << table_name << "\n"; + for (const auto& [prefix, path_information, labels] : prefixes) + { + std::cout << "\t\t\tprefix=" << prefix.toString() << ", path_information=" + << path_information << ", labels=" << labels << "\n"; + } + } + } + } + else + { + std::cout << "bad action type\n"; + } + } + + requests.clear(); + std::cout.flush(); +} + +int main(int argc, char** argv) +{ + if (argc != 2) + { + std::cout << "Usage: " << argv[0] << " \n"; + return 1; + } + + const char* sock_name = argv[1]; + const char* vrf = "default"; + read_bird_feed(sock_name, vrf, PrintRibRequests); + + return 0; +} diff --git a/demo/bird_app/build/.gitignore b/demo/bird_app/build/.gitignore new file mode 100644 index 00000000..5816188c --- /dev/null +++ b/demo/bird_app/build/.gitignore @@ -0,0 +1 @@ +bird_app