From a83d9d67113dc75d21a68dba14891ed5e1bea7ec Mon Sep 17 00:00:00 2001 From: Neale Ranns Date: Wed, 19 Jun 2019 08:30:46 -0700 Subject: Initial Commit of VPP OPflex Renderer Change-Id: I6264537538ad2646cddfa404de38a6bbf3abaa35 Signed-off-by: Neale Ranns --- src/VppEndPointManager.cpp | 741 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 741 insertions(+) create mode 100644 src/VppEndPointManager.cpp (limited to 'src/VppEndPointManager.cpp') diff --git a/src/VppEndPointManager.cpp b/src/VppEndPointManager.cpp new file mode 100644 index 0000000..4be8f70 --- /dev/null +++ b/src/VppEndPointManager.cpp @@ -0,0 +1,741 @@ +/* -*- C++ -*-; c-basic-offset: 4; indent-tabs-mode: nil */ +/* + * Copyright (c) 2017-2018 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "VppEndPointGroupManager.hpp" +#include "VppEndPointManager.hpp" +#include "VppLog.hpp" +#include "VppSecurityGroupManager.hpp" +#include "VppUtil.hpp" + +using namespace VOM; +using namespace boost; + +namespace VPP +{ +EndPointManager::EndPointManager(Runtime &runtime) + : m_runtime(runtime) +{ +} + +EndPointManager::~EndPointManager() +{ +} + +std::string +EndPointManager::get_ep_interface_name(const opflexagent::Endpoint &ep) throw( + NoEpInterfaceException) +{ + const optional &epAccessItf = ep.getAccessInterface(); + const optional &epItf = ep.getInterfaceName(); + const std::string uuid = ep.getUUID(); + std::string iname; + + /* + * the goal here is to get the name of the interface to which the VM + * is attached. + */ + if (epAccessItf) + iname = epAccessItf.get(); + else if (epItf) + iname = epItf.get(); + else + throw NoEpInterfaceException(); + + return iname; +} + +std::shared_ptr +EndPointManager::mk_bd_interface( + const opflexagent::Endpoint &ep, + const std::shared_ptr bd, + const std::shared_ptr rd) throw(NoEpInterfaceException) +{ + const optional &epAccessItf = ep.getAccessInterface(); + const optional &epItf = ep.getInterfaceName(); + const std::string uuid = ep.getUUID(); + std::string iname = get_ep_interface_name(ep); + std::shared_ptr itf; + + if (ep.getAccessIfaceVlan()) + { + uint16_t vlan_id; + interface intf(iname, + getIntfTypeFromName(iname), + interface::admin_state_t::UP, + uuid); + OM::write(uuid, intf); + + vlan_id = ep.getAccessIfaceVlan().get(); + sub_interface sub_itf(intf, interface::admin_state_t::UP, *rd, vlan_id); + OM::write(uuid, sub_itf); + itf = sub_itf.singular(); + + /* + * EP's interface is in the EPG's BD + */ + l2_binding l2itf(*itf, *bd); + if (ep.getAccessIfaceVlan()) + { + l2itf.set(l2_vtr::option_t::POP_1, vlan_id); + } + + OM::write(uuid, l2itf); + } + else + { + interface intf(iname, + getIntfTypeFromName(iname), + interface::admin_state_t::UP, + *rd, + uuid); + OM::write(uuid, intf); + itf = intf.singular(); + } + + /* + * If the interface is not created then we cannot do anymore + */ + if (handle_t::INVALID == itf->handle()) throw NoEpInterfaceException(); + + return itf; +} + +static void +allow_dhcp_request(ACL::l3_list::rules_t &in_rules, + ACL::l3_list::rules_t &out_rules, + uint16_t etherType) +{ + + ACL::action_t act = ACL::action_t::PERMIT; + + if (etherType == modelgbp::l2::EtherTypeEnumT::CONST_IPV4) + { + route::prefix_t pfx = route::prefix_t::ZERO; + + ACL::l3_rule rule(200, act, pfx, pfx); + + rule.set_proto(17); + rule.set_src_from_port(68); + rule.set_src_to_port(68); + rule.set_dst_from_port(67); + rule.set_dst_to_port(67); + + in_rules.insert(rule); + + ACL::l3_rule out_rule(200, act, pfx, pfx); + + out_rule.set_proto(17); + out_rule.set_src_from_port(67); + out_rule.set_src_to_port(67); + out_rule.set_dst_from_port(68); + out_rule.set_dst_to_port(68); + + out_rules.insert(out_rule); + } + else + { + route::prefix_t pfx = route::prefix_t::ZEROv6; + + ACL::l3_rule rule(200, act, pfx, pfx); + + rule.set_proto(17); + rule.set_src_from_port(546); + rule.set_src_to_port(546); + rule.set_dst_from_port(547); + rule.set_dst_to_port(547); + + in_rules.insert(rule); + + ACL::l3_rule out_rule(200, act, pfx, pfx); + + out_rule.set_proto(17); + out_rule.set_src_from_port(547); + out_rule.set_src_to_port(547); + out_rule.set_dst_from_port(546); + out_rule.set_dst_to_port(546); + + out_rules.insert(out_rule); + } +} + +static std::vector +get_ep_ips(const opflexagent::Endpoint &ep) +{ + /* check and parse the IP-addresses */ + system::error_code ec; + std::vector ipAddresses; + + const optional mac = ep.getMAC(); + + for (const std::string &ipStr : ep.getIPs()) + { + asio::ip::address addr = asio::ip::address::from_string(ipStr, ec); + if (ec) + { + LOG(opflexagent::WARNING) << "Invalid endpoint IP: " << ipStr + << ": " << ec.message(); + } + else + { + ipAddresses.push_back(addr); + } + } + + if (mac) + { + asio::ip::address_v6 linkLocalIp( + opflexagent::network::construct_link_local_ip_addr(mac.get())); + if (ep.getIPs().find(linkLocalIp.to_string()) == ep.getIPs().end()) + ipAddresses.push_back(linkLocalIp); + } + + return ipAddresses; +} + +void +EndPointManager::handle_interface_stat_i(const interface &itf) +{ + VLOGD << "Interface Stat: " << itf.to_string(); + + opflexagent::EndpointManager &epMgr = m_runtime.agent.getEndpointManager(); + + opflexagent::EndpointManager::EpCounters counters; + std::unordered_set endpoints; + auto &data = itf.get_stats(); + + VLOGD << "Stats data: " << data; + + epMgr.getEndpointsByAccessIface(itf.name(), endpoints); + + memset(&counters, 0, sizeof(counters)); + counters.txPackets = data.m_tx.packets; + counters.rxPackets = data.m_rx.packets; + counters.txBytes = data.m_tx.bytes; + counters.rxBytes = data.m_rx.bytes; + counters.rxUnicast = data.m_rx_unicast.packets; + counters.txUnicast = data.m_tx_unicast.packets; + counters.rxBroadcast = data.m_rx_broadcast.packets; + counters.txBroadcast = data.m_tx_broadcast.packets; + counters.rxMulticast = data.m_rx_multicast.packets; + counters.txMulticast = data.m_tx_multicast.packets; + // counters.txDrop = data.tx_dropped; + // counters.rxDrop = data.rx_dropped; + + for (const std::string &uuid : endpoints) + { + if (counters.rxDrop == std::numeric_limits::max()) + counters.rxDrop = 0; + if (counters.txDrop == std::numeric_limits::max()) + counters.txDrop = 0; + if (counters.txPackets == std::numeric_limits::max()) + counters.txPackets = 0; + if (counters.rxPackets == std::numeric_limits::max()) + counters.rxPackets = 0; + if (counters.txBroadcast == std::numeric_limits::max()) + counters.txBroadcast = 0; + if (counters.rxBroadcast == std::numeric_limits::max()) + counters.rxBroadcast = 0; + if (counters.txMulticast == std::numeric_limits::max()) + counters.txMulticast = 0; + if (counters.rxMulticast == std::numeric_limits::max()) + counters.rxMulticast = 0; + if (counters.txUnicast == std::numeric_limits::max()) + counters.txUnicast = 0; + if (counters.rxUnicast == std::numeric_limits::max()) + counters.rxUnicast = 0; + if (counters.rxBytes == std::numeric_limits::max()) + counters.rxBytes = 0; + if (counters.txBytes == std::numeric_limits::max()) + counters.txBytes = 0; + epMgr.updateEndpointCounters(uuid, counters); + } +} + +void +EndPointManager::handle_interface_stat(const interface &itf) +{ + handle_interface_stat_i(itf); +} + +void +EndPointManager::handle_update_i(const std::string &uuid, bool is_external) +{ + /* + * This is an update to all the state related to this endpoint. + * At the end of processing we want all the state related to this endpint, + * that we don't touch here, gone. + */ + OM::mark_n_sweep ms(uuid); + system::error_code ec; + int rv; + + opflexagent::EndpointManager &epMgr = m_runtime.agent.getEndpointManager(); + std::shared_ptr epWrapper = + epMgr.getEndpoint(uuid); + + if (!epWrapper) + { + VLOGD << "Deleting endpoint " << uuid; + return; + } + VLOGD << "Updating endpoint " << uuid; + + optional epgURI = epMgr.getComputedEPG(uuid); + + if (!epgURI) + { + // can't do much without EPG + VLOGD << "Endpoint - no EPG " << uuid; + return; + } + + if (is_external && !epWrapper->isExternal()) + { + VLOGE << "Endpoint - not external " << uuid; + return; + } + + std::shared_ptr gepg = + EndPointGroupManager::mk_group( + m_runtime, uuid, epgURI.get(), is_external); + + if (gepg) + { + std::shared_ptr bvi = gepg->get_bridge_domain()->get_bvi(); + std::shared_ptr bd = + gepg->get_bridge_domain()->get_bridge_domain(); + std::shared_ptr rd = + gepg->get_route_domain()->get_route_domain(); + + /* + * We want a veth interface - admin up + */ + std::shared_ptr itf; + try + { + const opflexagent::Endpoint &ep = *epWrapper.get(); + + itf = mk_bd_interface(ep, bd, rd); + + /** + * We are interested in getting detailed interface stats from VPP + */ + itf->enable_stats(this); + + /* + * Apply Security Groups + */ + const opflexagent::EndpointListener::uri_set_t &secGrps = + ep.getSecurityGroups(); + const std::string secGrpId = SecurityGroupManager::get_id(secGrps); + std::hash string_hash; + const std::string secGrpKey = std::to_string(string_hash(secGrpId)); + + ACL::l3_list::rules_t in_rules, out_rules; + ACL::acl_ethertype::ethertype_rules_t ethertype_rules; + + optional v4c = + ep.getDHCPv4Config(); + if (v4c) + { + ACL::ethertype_rule_t et(ethertype_t::IPV4, direction_t::INPUT); + ethertype_rules.insert(et); + ACL::ethertype_rule_t out_et(ethertype_t::IPV4, + direction_t::OUTPUT); + ethertype_rules.insert(out_et); + allow_dhcp_request(in_rules, + out_rules, + modelgbp::l2::EtherTypeEnumT::CONST_IPV4); + } + optional v6c = + ep.getDHCPv6Config(); + if (v6c) + { + ACL::ethertype_rule_t et(ethertype_t::IPV6, direction_t::INPUT); + ethertype_rules.insert(et); + ACL::ethertype_rule_t out_et(ethertype_t::IPV6, + direction_t::OUTPUT); + ethertype_rules.insert(out_et); + allow_dhcp_request(in_rules, + out_rules, + modelgbp::l2::EtherTypeEnumT::CONST_IPV6); + } + + SecurityGroupManager::build_update(m_runtime.agent, + secGrps, + secGrpId, + in_rules, + out_rules, + ethertype_rules); + + if (!ethertype_rules.empty()) + { + ACL::acl_ethertype a_e(*itf, ethertype_rules); + OM::write(uuid, a_e); + } + if (!in_rules.empty()) + { + ACL::l3_list in_acl(secGrpKey + "-in", in_rules); + OM::write(uuid, in_acl); + + ACL::l3_binding in_binding(direction_t::INPUT, *itf, in_acl); + OM::write(uuid, in_binding); + } + if (!out_rules.empty()) + { + ACL::l3_list out_acl(secGrpKey + "-out", out_rules); + OM::write(uuid, out_acl); + + ACL::l3_binding out_binding(direction_t::OUTPUT, *itf, out_acl); + OM::write(uuid, out_binding); + } + + uint8_t macAddr[6] = {0}; + bool hasMac = ep.getMAC() != none; + + if (hasMac) ep.getMAC().get().toUIntArray(macAddr); + + /* check and parse the IP-addresses */ + std::vector ipAddresses = get_ep_ips(ep); + + ACL::l2_list::rules_t rules; + if (itf->handle().value()) + { + if (ep.isPromiscuousMode()) + { + ACL::l2_rule rulev6(50, + ACL::action_t::PERMIT, + route::prefix_t::ZEROv6, + macAddr, + mac_address_t::ZERO); + + ACL::l2_rule rulev4(51, + ACL::action_t::PERMIT, + route::prefix_t::ZERO, + macAddr, + mac_address_t::ZERO); + rules.insert(rulev4); + rules.insert(rulev6); + } + else if (hasMac) + { + ACL::l2_rule rulev6(20, + ACL::action_t::PERMIT, + route::prefix_t::ZEROv6, + macAddr, + mac_address_t::ONE); + + ACL::l2_rule rulev4(21, + ACL::action_t::PERMIT, + route::prefix_t::ZERO, + macAddr, + mac_address_t::ONE); + rules.insert(rulev4); + rules.insert(rulev6); + + for (auto &ipAddr : ipAddresses) + { + // Allow IPv4/IPv6 packets from port with EP IP address + route::prefix_t pfx(ipAddr, ipAddr.is_v4() ? 32 : 128); + if (ipAddr.is_v6()) + { + ACL::l2_rule rule(30, + ACL::action_t::PERMIT, + pfx, + macAddr, + mac_address_t::ONE); + rules.insert(rule); + } + else + { + ACL::l2_rule rule(31, + ACL::action_t::PERMIT, + pfx, + macAddr, + mac_address_t::ONE); + rules.insert(rule); + } + } + } + + for (const opflexagent::Endpoint::virt_ip_t &vip : + ep.getVirtualIPs()) + { + opflexagent::network::cidr_t vip_cidr; + if (!opflexagent::network::cidr_from_string(vip.second, + vip_cidr)) + { + LOG(opflexagent::WARNING) + << "Invalid endpoint VIP (CIDR): " << vip.second; + continue; + } + uint8_t vmac[6]; + vip.first.toUIntArray(vmac); + + for (auto &ipAddr : ipAddresses) + { + if (!opflexagent::network::cidr_contains(vip_cidr, + ipAddr)) + { + continue; + } + route::prefix_t pfx(ipAddr, ipAddr.is_v4() ? 32 : 128); + if (ipAddr.is_v6()) + { + ACL::l2_rule rule(60, + ACL::action_t::PERMIT, + pfx, + vmac, + mac_address_t::ONE); + rules.insert(rule); + } + else + { + ACL::l2_rule rule(61, + ACL::action_t::PERMIT, + pfx, + vmac, + mac_address_t::ONE); + rules.insert(rule); + } + } + } + + ACL::l2_list acl(uuid, rules); + OM::write(uuid, acl); + + ACL::l2_binding binding(direction_t::INPUT, *itf, acl); + OM::write(uuid, binding); + } + + if (hasMac) + { + mac_address_t vmac(macAddr); + + /* + * add a GDBP endpoint + */ + gbp_endpoint gbpe(*itf, + ipAddresses, + vmac, + *gepg, + (is_external ? gbp_endpoint::flags_t::EXTERNAL + : gbp_endpoint::flags_t::NONE)); + OM::write(uuid, gbpe); + + /* + * Floating IP addresses -> NAT + */ + if (m_runtime.vr && + (modelgbp::gbp::RoutingModeEnumT::CONST_ENABLED == + m_runtime.agent.getPolicyManager().getEffectiveRoutingMode( + epgURI.get()))) + { + auto ipms = ep.getIPAddressMappings(); + + if (0 != ipms.size()) + { + /* + * there are floating IPs, we need a recirulation + * interface + * for this EP's EPG. These are NAT outside and input + * feautre + * since packets are sent to these interface in order to + * have + * the out2in translation applied. + */ + interface recirc_itf("recirc-" + + std::to_string(gepg->sclass()), + interface::type_t::LOOPBACK, + interface::admin_state_t::UP, + *rd); + OM::write(uuid, recirc_itf); + + l2_binding recirc_l2b(recirc_itf, *bd); + OM::write(uuid, recirc_l2b); + + nat_binding recirc_nb4(recirc_itf, + direction_t::INPUT, + l3_proto_t::IPV4, + nat_binding::zone_t::OUTSIDE); + OM::write(uuid, recirc_nb4); + + nat_binding recirc_nb6(recirc_itf, + direction_t::INPUT, + l3_proto_t::IPV6, + nat_binding::zone_t::OUTSIDE); + OM::write(uuid, recirc_nb6); + + gbp_recirc grecirc( + recirc_itf, gbp_recirc::type_t::INTERNAL, *gepg); + OM::write(uuid, grecirc); + + for (auto &ipm : ipms) + { + if (!ipm.getMappedIP() || !ipm.getEgURI()) continue; + + asio::ip::address mappedIp = + asio::ip::address::from_string( + ipm.getMappedIP().get(), ec); + if (ec) continue; + + asio::ip::address floatingIp; + if (ipm.getFloatingIP()) + { + floatingIp = asio::ip::address::from_string( + ipm.getFloatingIP().get(), ec); + if (ec) continue; + if (floatingIp.is_v4() != mappedIp.is_v4()) + continue; + } + + EndPointGroupManager::ForwardInfo ffwd; + + try + { + ffwd = EndPointGroupManager::get_fwd_info( + m_runtime, ipm.getEgURI().get()); + + VLOGD << "EP:" << uuid << " - add Floating IP" + << floatingIp << " => " << mappedIp; + + /* + * Route and Bridge Domains and the external EPG + */ + route_domain ext_rd(ffwd.rdId); + OM::write(uuid, ext_rd); + bridge_domain ext_bd( + ffwd.bdId, + bridge_domain::learning_mode_t::OFF); + OM::write(uuid, ext_bd); + interface ext_bvi("bvi-" + + std::to_string(ffwd.bdId), + interface::type_t::BVI, + interface::admin_state_t::UP, + ext_rd); + OM::write(uuid, ext_bvi); + + /* + * Route for the floating IP via the internal + * EPG's recirc + */ + route::prefix_t fp_pfx(floatingIp); + route::ip_route fp_route( + ext_rd, + fp_pfx, + {recirc_itf, + fp_pfx.l3_proto().to_nh_proto(), + route::path::flags_t::DVR}); + OM::write(uuid, fp_route); + + neighbour fp_ne(ext_bvi, floatingIp, {macAddr}); + OM::write(uuid, fp_ne); + + /* + * reply to ARP's for the floating IP + */ + bridge_domain_arp_entry fp_bae( + ext_bd, floatingIp, {macAddr}); + OM::write(uuid, fp_bae); + + /* + * Bridge L2 packets addressed to the VM to the + * recirc + * interface + */ + bridge_domain_entry fp_be( + ext_bd, macAddr, recirc_itf); + OM::write(uuid, fp_be); + + /* + * NAT static mapping + */ + nat_static ns(*rd, mappedIp, floatingIp); + OM::write(uuid, ns); + } + catch (EndPointGroupManager::NoFowardInfoException + &nofwd) + { + VLOGD << "Endpoint Floating IP no fwd: " + << nofwd.reason << " : " << uuid; + } + } + } + } + } + } + catch (EndPointManager::NoEpInterfaceException &noepitf) + { + VLOGD << "Endpoint - no interface " << uuid; + } + + /* + * If the EP is external then its EPG's BD interface is an external + * interface + */ + if (is_external) + { + gbp_ext_itf gei( + *bvi, *gepg->get_bridge_domain(), *gepg->get_route_domain()); + OM::write(uuid, gei); + } + } + + /* + * That's all folks ... destructor of mark_n_sweep calls the + * sweep for the stale state + */ +} +void +EndPointManager::handle_update(const std::string &uuid) +{ + handle_update_i(uuid, false); +} +void +EndPointManager::handle_external_update(const std::string &uuid) +{ + handle_update_i(uuid, true); +} + +void +EndPointManager::handle_remote_update(const std::string &uuid) +{ + VLOGE << "Endpoint - remote not supported: " << uuid; +} + +}; // namespace VPP -- cgit 1.2.3-korg