diff --git a/.gitignore b/.gitignore index 736b86b5a3..7b0ae36d2a 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,7 @@ cfgmgr/intfmgrd cfgmgr/vlanmgrd cfgmgr/buffermanager cfgmgr/vrfmgrd +cfgmgr/nbrmgrd neighsyncd/neighsyncd portsyncd/portsyncd orchagent/orchagent diff --git a/cfgmgr/Makefile.am b/cfgmgr/Makefile.am index 4660fd40a3..e2e68d976c 100644 --- a/cfgmgr/Makefile.am +++ b/cfgmgr/Makefile.am @@ -1,7 +1,9 @@ INCLUDES = -I $(top_srcdir) -I $(top_srcdir)/orchagent -I $(top_srcdir)/warmrestart CFLAGS_SAI = -I /usr/include/sai +LIBNL_CFLAGS = -I/usr/include/libnl3 +LIBNL_LIBS = -lnl-genl-3 -lnl-route-3 -lnl-3 -bin_PROGRAMS = vlanmgrd teammgrd portmgrd intfmgrd buffermgrd vrfmgrd +bin_PROGRAMS = vlanmgrd teammgrd portmgrd intfmgrd buffermgrd vrfmgrd nbrmgrd if DEBUG DBGFLAGS = -ggdb -DDEBUG @@ -38,3 +40,8 @@ vrfmgrd_SOURCES = vrfmgrd.cpp vrfmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_ vrfmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) vrfmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) vrfmgrd_LDADD = -lswsscommon + +nbrmgrd_SOURCES = nbrmgrd.cpp nbrmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp shellcmd.h +nbrmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(LIBNL_CFLAGS) +nbrmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(LIBNL_CPPFLAGS) +nbrmgrd_LDADD = -lswsscommon $(LIBNL_LIBS) \ No newline at end of file diff --git a/cfgmgr/nbrmgr.cpp b/cfgmgr/nbrmgr.cpp new file mode 100644 index 0000000000..d2ca7ebc96 --- /dev/null +++ b/cfgmgr/nbrmgr.cpp @@ -0,0 +1,263 @@ +#include +#include +#include +#include + +#include "logger.h" +#include "tokenize.h" +#include "ipprefix.h" +#include "macaddress.h" +#include "nbrmgr.h" +#include "exec.h" +#include "shellcmd.h" + +using namespace swss; + +#define VLAN_PREFIX "Vlan" +#define LAG_PREFIX "PortChannel" + +static bool send_message(struct nl_sock *sk, struct nl_msg *msg) +{ + bool rc = false; + int err = 0; + + do + { + if (!sk) + { + SWSS_LOG_ERROR("Netlink socket null pointer"); + break; + } + + if ((err = nl_send_auto(sk, msg)) < 0) + { + SWSS_LOG_ERROR("Netlink send message failed, error '%s'", nl_geterror(err)); + break; + } + + rc = true; + } while(0); + + nlmsg_free(msg); + return rc; +} + +NbrMgr::NbrMgr(DBConnector *cfgDb, DBConnector *appDb, DBConnector *stateDb, const vector &tableNames) : + Orch(cfgDb, tableNames), + m_statePortTable(stateDb, STATE_PORT_TABLE_NAME), + m_stateLagTable(stateDb, STATE_LAG_TABLE_NAME), + m_stateVlanTable(stateDb, STATE_VLAN_TABLE_NAME), + m_stateIntfTable(stateDb, STATE_INTERFACE_TABLE_NAME) +{ + int err = 0; + + m_nl_sock = nl_socket_alloc(); + if (!m_nl_sock) + { + SWSS_LOG_ERROR("Netlink socket alloc failed"); + } + else if ((err = nl_connect(m_nl_sock, NETLINK_ROUTE)) < 0) + { + SWSS_LOG_ERROR("Netlink socket connect failed, error '%s'", nl_geterror(err)); + } +} + +bool NbrMgr::isIntfStateOk(const string &alias) +{ + vector temp; + + if (!alias.compare(0, strlen(VLAN_PREFIX), VLAN_PREFIX)) + { + if (m_stateVlanTable.get(alias, temp)) + { + SWSS_LOG_DEBUG("Vlan %s is ready", alias.c_str()); + return true; + } + } + else if (!alias.compare(0, strlen(LAG_PREFIX), LAG_PREFIX)) + { + if (m_stateLagTable.get(alias, temp)) + { + SWSS_LOG_DEBUG("Lag %s is ready", alias.c_str()); + return true; + } + } + else if (m_statePortTable.get(alias, temp)) + { + SWSS_LOG_DEBUG("Port %s is ready", alias.c_str()); + return true; + } + + return false; +} + +bool NbrMgr::setNeighbor(const string& alias, const IpAddress& ip, const MacAddress& mac) +{ + SWSS_LOG_ENTER(); + + struct nl_msg *msg = nlmsg_alloc(); + if (!msg) + { + SWSS_LOG_ERROR("Netlink message alloc failed for '%s'", ip.to_string().c_str()); + return false; + } + + auto flags = (NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_REPLACE); + + struct nlmsghdr *hdr = nlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, RTM_NEWNEIGH, 0, flags); + if (!hdr) + { + SWSS_LOG_ERROR("Netlink message header alloc failed for '%s'", ip.to_string().c_str()); + nlmsg_free(msg); + return false; + } + + struct ndmsg *nd_msg = static_cast + (nlmsg_reserve(msg, sizeof(struct ndmsg), NLMSG_ALIGNTO)); + if (!nd_msg) + { + SWSS_LOG_ERROR("Netlink ndmsg reserve failed for '%s'", ip.to_string().c_str()); + nlmsg_free(msg); + return false; + } + + memset(nd_msg, 0, sizeof(struct ndmsg)); + + nd_msg->ndm_ifindex = if_nametoindex(alias.c_str()); + + auto addr_len = ip.isV4()? sizeof(struct in_addr) : sizeof(struct in6_addr); + + struct rtattr *rta = static_cast + (nlmsg_reserve(msg, sizeof(struct rtattr) + addr_len, NLMSG_ALIGNTO)); + if (!rta) + { + SWSS_LOG_ERROR("Netlink rtattr (IP) failed for '%s'", ip.to_string().c_str()); + nlmsg_free(msg); + return false; + } + + rta->rta_type = NDA_DST; + rta->rta_len = static_cast(RTA_LENGTH(addr_len)); + + nd_msg->ndm_type = RTN_UNICAST; + auto ip_addr = ip.getIp(); + + if (ip.isV4()) + { + nd_msg->ndm_family = AF_INET; + memcpy(RTA_DATA(rta), &ip_addr.ip_addr.ipv4_addr, addr_len); + } + else + { + nd_msg->ndm_family = AF_INET6; + memcpy(RTA_DATA(rta), &ip_addr.ip_addr.ipv6_addr, addr_len); + } + + if (!mac) + { + /* + * If mac is not provided, expected to resolve the MAC + */ + nd_msg->ndm_state = NUD_DELAY; + nd_msg->ndm_flags = NTF_USE; + + SWSS_LOG_INFO("Resolve request for '%s'", ip.to_string().c_str()); + } + else + { + SWSS_LOG_INFO("Set mac address '%s'", mac.to_string().c_str()); + + nd_msg->ndm_state = NUD_PERMANENT; + + auto mac_len = ETHER_ADDR_LEN; + auto mac_addr = mac.getMac(); + + struct rtattr *rta = static_cast + (nlmsg_reserve(msg, sizeof(struct rtattr) + mac_len, NLMSG_ALIGNTO)); + if (!rta) + { + SWSS_LOG_ERROR("Netlink rtattr (MAC) failed for '%s'", ip.to_string().c_str()); + nlmsg_free(msg); + return false; + } + + rta->rta_type = NDA_LLADDR; + rta->rta_len = static_cast(RTA_LENGTH(mac_len)); + memcpy(RTA_DATA(rta), mac_addr, mac_len); + } + + return send_message(m_nl_sock, msg); +} + +void NbrMgr::doTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + KeyOpFieldsValuesTuple t = it->second; + vector keys = tokenize(kfvKey(t), config_db_key_delimiter); + const vector& data = kfvFieldsValues(t); + + string alias(keys[0]); + IpAddress ip(keys[1]); + string op = kfvOp(t); + MacAddress mac; + bool invalid_mac = false; + + for (auto idx : data) + { + const auto &field = fvField(idx); + const auto &value = fvValue(idx); + if (field == "neigh") + { + try + { + mac = value; + } + catch (const std::invalid_argument& e) + { + SWSS_LOG_ERROR("Invalid Mac addr '%s' for '%s'", value.c_str(), kfvKey(t).c_str()); + invalid_mac = true; + break; + } + } + } + + if (invalid_mac) + { + it = consumer.m_toSync.erase(it); + continue; + } + + if (op == SET_COMMAND) + { + if (!isIntfStateOk(alias)) + { + SWSS_LOG_DEBUG("Interface is not yet ready, skipping '%s'", kfvKey(t).c_str()); + it++; + continue; + } + + if (!setNeighbor(alias, ip, mac)) + { + SWSS_LOG_ERROR("Neigh entry add failed for '%s'", kfvKey(t).c_str()); + } + else + { + SWSS_LOG_NOTICE("Neigh entry added for '%s'", kfvKey(t).c_str()); + } + } + else if (op == DEL_COMMAND) + { + SWSS_LOG_NOTICE("Not yet implemented, key '%s'", kfvKey(t).c_str()); + } + else + { + SWSS_LOG_ERROR("Unknown operation: '%s'", op.c_str()); + } + + it = consumer.m_toSync.erase(it); + } +} diff --git a/cfgmgr/nbrmgr.h b/cfgmgr/nbrmgr.h new file mode 100644 index 0000000000..61fba1bf7b --- /dev/null +++ b/cfgmgr/nbrmgr.h @@ -0,0 +1,35 @@ +#ifndef __NBRMGR__ +#define __NBRMGR__ + +#include +#include +#include + +#include "dbconnector.h" +#include "producerstatetable.h" +#include "orch.h" +#include "netmsg.h" + +using namespace std; + +namespace swss { + +class NbrMgr : public Orch +{ +public: + NbrMgr(DBConnector *cfgDb, DBConnector *appDb, DBConnector *stateDb, const vector &tableNames); + using Orch::doTask; + +private: + bool isIntfStateOk(const string &alias); + bool setNeighbor(const string& alias, const IpAddress& ip, const MacAddress& mac); + + void doTask(Consumer &consumer); + + Table m_statePortTable, m_stateLagTable, m_stateVlanTable, m_stateIntfTable; + struct nl_sock *m_nl_sock; +}; + +} + +#endif // __NBRMGR__ diff --git a/cfgmgr/nbrmgrd.cpp b/cfgmgr/nbrmgrd.cpp new file mode 100644 index 0000000000..2872fa9e67 --- /dev/null +++ b/cfgmgr/nbrmgrd.cpp @@ -0,0 +1,88 @@ +#include +#include +#include +#include +#include + +#include "select.h" +#include "exec.h" +#include "schema.h" +#include "nbrmgr.h" + +using namespace std; +using namespace swss; + +/* select() function timeout retry time, in millisecond */ +#define SELECT_TIMEOUT 1000 + +/* + * Following global variables are defined here for the purpose of + * using existing Orch class which is to be refactored soon to + * eliminate the direct exposure of the global variables. + * + * Once Orch class refactoring is done, these global variables + * should be removed from here. + */ +int gBatchSize = 0; +bool gSwssRecord = false; +bool gLogRotate = false; +ofstream gRecordOfs; +string gRecordFile; +/* Global database mutex */ +mutex gDbMutex; + +int main(int argc, char **argv) +{ + Logger::linkToDbNative("nbrmgrd"); + SWSS_LOG_ENTER(); + + SWSS_LOG_NOTICE("--- Starting nbrmgrd ---"); + + try + { + vector cfg_nbr_tables = { + CFG_NEIGH_TABLE_NAME, + }; + + DBConnector cfgDb(CONFIG_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); + DBConnector appDb(APPL_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); + DBConnector stateDb(STATE_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); + + NbrMgr nbrmgr(&cfgDb, &appDb, &stateDb, cfg_nbr_tables); + + std::vector cfgOrchList = {&nbrmgr}; + + swss::Select s; + for (Orch *o : cfgOrchList) + { + s.addSelectables(o->getSelectables()); + } + + SWSS_LOG_NOTICE("starting main loop"); + while (true) + { + Selectable *sel; + int ret; + + ret = s.select(&sel, SELECT_TIMEOUT); + if (ret == Select::ERROR) + { + SWSS_LOG_NOTICE("Error: %s!", strerror(errno)); + continue; + } + if (ret == Select::TIMEOUT) + { + nbrmgr.doTask(); + continue; + } + + auto *c = (Executor *)sel; + c->execute(); + } + } + catch(const std::exception &e) + { + SWSS_LOG_ERROR("Runtime error: %s", e.what()); + } + return -1; +} diff --git a/doc/swss-schema.md b/doc/swss-schema.md index 085fd66abd..6a43b9f1dc 100644 --- a/doc/swss-schema.md +++ b/doc/swss-schema.md @@ -705,6 +705,11 @@ Status: ready vni = uint24 ; vni id, defined for tunnel map vlan = "Vlan"vlan_id ; name of the existing vlan interface +### NEIGH_TABLE + ; Stores the neighbors. Defines static configuration of neighbor entries. If mac address is not specified, implementation shall resolve the mac-address for the neighbor IP. + key = NEIGH|PORT_TABLE.name / VLAN_INTF_TABLE.name / LAG_INTF_TABLE.name|prefix + neigh = 12HEXDIG ; mac address of the neighbor (optional) + family = "IPv4" / "IPv6" ; address family ## State DB schema