diff options
Diffstat (limited to 'src/VppEndPointGroupManager.cpp')
-rw-r--r-- | src/VppEndPointGroupManager.cpp | 592 |
1 files changed, 592 insertions, 0 deletions
diff --git a/src/VppEndPointGroupManager.cpp b/src/VppEndPointGroupManager.cpp new file mode 100644 index 0000000..38c3e27 --- /dev/null +++ b/src/VppEndPointGroupManager.cpp @@ -0,0 +1,592 @@ +/* -*- C++ -*-; c-basic-offset: 4; indent-tabs-mode: nil */ +/* + * Copyright (c) 2017 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 <boost/optional.hpp> + +#include <opflexagent/Agent.h> +#include <opflexagent/PolicyManager.h> + +#include <modelgbp/gbp/AddressResModeEnumT.hpp> +#include <modelgbp/gbp/BcastFloodModeEnumT.hpp> +#include <modelgbp/gbp/BridgeDomain.hpp> +#include <modelgbp/gbp/EpLearningModeEnumT.hpp> +#include <modelgbp/gbp/FloodDomain.hpp> +#include <modelgbp/gbp/RoutingDomain.hpp> +#include <modelgbp/gbp/UnknownFloodModeEnumT.hpp> + +#include <vom/bridge_domain.hpp> +#include <vom/bridge_domain_arp_entry.hpp> +#include <vom/bridge_domain_entry.hpp> +#include <vom/gbp_endpoint_group.hpp> +#include <vom/gbp_subnet.hpp> +#include <vom/gbp_vxlan.hpp> +#include <vom/igmp_binding.hpp> +#include <vom/igmp_listen.hpp> +#include <vom/l2_binding.hpp> +#include <vom/l3_binding.hpp> +#include <vom/nat_binding.hpp> +#include <vom/nat_static.hpp> +#include <vom/neighbour.hpp> +#include <vom/om.hpp> +#include <vom/route_domain.hpp> +#include <vom/vxlan_tunnel.hpp> + +#include "VppEndPointGroupManager.hpp" +#include "VppLog.hpp" +#include "VppSpineProxy.hpp" + +namespace VPP +{ +EndPointGroupManager::EndPointGroupManager(Runtime &runtime) + : m_runtime(runtime) +{ +} + +EndPointGroupManager::ForwardInfo::ForwardInfo() + : vnid(0xfefefefe) + , rdId(0xfefefefe) + , bdId(0xfefefefe) +{ +} + +EndPointGroupManager::ForwardInfo +EndPointGroupManager::get_fwd_info_ext_itf( + Runtime &runtime, const opflex::modb::URI &uri) throw(NoFowardInfoException) +{ + EndPointGroupManager::ForwardInfo fwd; + opflexagent::PolicyManager &polMgr = runtime.policy_manager(); + + fwd.vnid = 0xdeadbeaf; + + boost::optional<uint32_t> sclass = + polMgr.getSclassForExternalInterface(uri); + + if (!sclass) + { + throw NoFowardInfoException("No Sclass for External-Interface"); + } + fwd.sclass = sclass.get(); + + boost::optional<std::shared_ptr<modelgbp::gbp::RoutingDomain>> epgRd = + polMgr.getRDForExternalInterface(uri); + boost::optional<std::shared_ptr<modelgbp::gbp::ExternalL3BridgeDomain>> + epgBd = polMgr.getBDForExternalInterface(uri); + + if (epgRd) + { + fwd.rdURI = epgRd.get()->getURI(); + if (fwd.rdURI) + fwd.rdId = runtime.id_gen.get( + modelgbp::gbp::RoutingDomain::CLASS_ID, fwd.rdURI.get()); + else + throw NoFowardInfoException("No RD-URI for External-Interface"); + } + else + { + throw NoFowardInfoException("No RD for External-Interface"); + } + + if (epgBd) + { + fwd.bdURI = epgBd.get()->getURI(); + fwd.bdId = runtime.id_gen.get(modelgbp::gbp::BridgeDomain::CLASS_ID, + fwd.bdURI.get()); + } + else + { + throw NoFowardInfoException("No BD for EPG"); + } + return fwd; +} + +EndPointGroupManager::ForwardInfo +EndPointGroupManager::get_fwd_info( + Runtime &runtime, const opflex::modb::URI &uri) throw(NoFowardInfoException) +{ + EndPointGroupManager::ForwardInfo fwd; + opflexagent::PolicyManager &polMgr = runtime.policy_manager(); + boost::optional<uint32_t> epgVnid = polMgr.getVnidForGroup(uri); + + if (!epgVnid) + { + throw NoFowardInfoException("No EPG VNID"); + } + fwd.vnid = epgVnid.get(); + + boost::optional<uint32_t> sclass = polMgr.getSclassForGroup(uri); + + if (!sclass) + { + throw NoFowardInfoException("No EPG Sclass"); + } + fwd.sclass = sclass.get(); + + boost::optional<std::shared_ptr<modelgbp::gbp::RoutingDomain>> epgRd = + polMgr.getRDForGroup(uri); + boost::optional<std::shared_ptr<modelgbp::gbp::BridgeDomain>> epgBd = + polMgr.getBDForGroup(uri); + + if (epgRd) + { + fwd.rdURI = epgRd.get()->getURI(); + if (fwd.rdURI) + fwd.rdId = runtime.id_gen.get( + modelgbp::gbp::RoutingDomain::CLASS_ID, fwd.rdURI.get()); + else + throw NoFowardInfoException("No RD-URI for EPG"); + } + else + { + throw NoFowardInfoException("No RD for EPG"); + } + + if (epgBd) + { + fwd.bdURI = epgBd.get()->getURI(); + fwd.bdId = runtime.id_gen.get(modelgbp::gbp::BridgeDomain::CLASS_ID, + fwd.bdURI.get()); + } + else + { + throw NoFowardInfoException("No BD for EPG"); + } + return fwd; +} + +std::shared_ptr<vxlan_tunnel> +EndPointGroupManager::mk_mcast_tunnel(Runtime &r, + const std::string &key, + uint32_t vni, + const std::string &maddr) +{ + /* + * Add the Vxlan mcast tunnel that will carry the broadcast + * and multicast traffic + */ + boost::asio::ip::address dst = boost::asio::ip::address::from_string(maddr); + + vxlan_tunnel vt(r.uplink.local_address(), + dst, + vni, + *r.uplink.local_interface(), + vxlan_tunnel::mode_t::GBP_L2); + OM::write(key, vt); + + /* + * add the mcast group to accept via the uplink and + * forward locally. + */ + route::path via_uplink(*r.uplink.local_interface(), nh_proto_t::IPV4); + route::ip_mroute mroute({dst.to_v4(), 32}); + + mroute.add(via_uplink, route::itf_flags_t::ACCEPT); + mroute.add({route::path::special_t::LOCAL}, route::itf_flags_t::FORWARD); + OM::write(key, mroute); + + /* + * join the group on the uplink interface + */ + igmp_binding igmp_b(*r.uplink.local_interface()); + OM::write(key, igmp_b); + + igmp_listen igmp_l(igmp_b, dst.to_v4()); + OM::write(key, igmp_l); + + return (vt.singular()); +} + +std::shared_ptr<VOM::interface> +EndPointGroupManager::mk_bvi(Runtime &r, + const std::string &key, + const bridge_domain &bd, + const route_domain &rd, + const boost::optional<mac_address_t> &mac) +{ + std::shared_ptr<interface> bvi = + std::make_shared<interface>("bvi-" + std::to_string(bd.id()), + interface::type_t::BVI, + interface::admin_state_t::UP, + rd); + if (mac) + { + bvi->set(mac.get()); + } + else if (r.vr) + { + /* + * Set the BVI's MAC address to the Virtual Router + * address, so packets destined to the VR are handled + * by layer 3. + */ + bvi->set(r.vr->mac()); + } + OM::write(key, *bvi); + + /* + * Add the BVI to the BD + */ + l2_binding l2_bvi(*bvi, bd); + OM::write(key, l2_bvi); + + /* + * the bridge is not in learning mode. So add an L2FIB entry for the BVI + */ + bridge_domain_entry be(bd, bvi->l2_address().to_mac(), *bvi); + OM::write(key, be); + + return bvi; +} + +std::shared_ptr<VOM::gbp_route_domain> +EndPointGroupManager::mk_gbp_rd(Runtime &r, + const std::string &key, + const VOM::route_domain &rd, + uint32_t vnid) +{ + std::shared_ptr<VOM::gbp_route_domain> grd; + std::shared_ptr<SpineProxy> spine_proxy = r.uplink.spine_proxy(); + + if (spine_proxy) + { + std::shared_ptr<vxlan_tunnel> vt_v4, vt_v6; + + vt_v4 = spine_proxy->mk_v4(key, vnid); + vt_v6 = spine_proxy->mk_v6(key, vnid); + + grd = std::make_shared<gbp_route_domain>(rd, vt_v4, vt_v6); + } + else + { + grd = std::make_shared<gbp_route_domain>(rd); + } + + OM::write(key, *grd); + + return grd; +} + +std::shared_ptr<VOM::gbp_endpoint_group> +EndPointGroupManager::mk_group(Runtime &runtime, + const std::string &key, + const opflex::modb::URI &uri, + bool is_ext) +{ + std::shared_ptr<VOM::gbp_endpoint_group> gepg; + + try + { + /* + * default retention policy of 2 minutes. + */ + EndPointGroupManager::ForwardInfo fwd; + gbp_endpoint_group::retention_t retention(120); + + if (is_ext) + fwd = get_fwd_info_ext_itf(runtime, uri); + else + fwd = get_fwd_info(runtime, uri); + + boost::optional<std::shared_ptr<modelgbp::gbpe::EndpointRetention>> + ret_pol = + runtime.policy_manager().getL2EPRetentionPolicyForGroup(uri); + + if (ret_pol) + { + retention.remote_ep_timeout = + ret_pol.get()->getRemoteEpAgingInterval(120); + } + + /* + * Construct the Bridge and routing Domains + */ + bridge_domain bd(fwd.bdId, bridge_domain::learning_mode_t::OFF); + OM::write(key, bd); + route_domain rd(fwd.rdId); + OM::write(key, rd); + + /* + * Create a BVI interface for the EPG and add it to the bridge-domain + */ + std::shared_ptr<interface> bvi = mk_bvi(runtime, key, bd, rd); + + std::shared_ptr<SpineProxy> spine_proxy = runtime.uplink.spine_proxy(); + + if (spine_proxy) + { + /* + * TRANSPORT mode + * then a route domain that uses the v4 and v6 resp + */ + boost::optional<uint32_t> rd_vnid; + boost::optional<uint32_t> bd_vnid; + boost::optional<std::string> bd_mcast; + if (is_ext) + { + rd_vnid = + runtime.policy_manager().getRDVnidForExternalInterface(uri); + bd_vnid = + runtime.policy_manager().getBDVnidForExternalInterface(uri); + bd_mcast = runtime.policy_manager() + .getBDMulticastIPForExternalInterface(uri); + } + else + { + rd_vnid = runtime.policy_manager().getRDVnidForGroup(uri); + bd_vnid = runtime.policy_manager().getBDVnidForGroup(uri); + bd_mcast = + runtime.policy_manager().getBDMulticastIPForGroup(uri); + } + + if (bd_vnid && rd_vnid && bd_mcast) + { + std::shared_ptr<vxlan_tunnel> vt_mc, vt_mac; + gbp_bridge_domain::flags_t gbd_flags = + gbp_bridge_domain::flags_t::NONE; + + boost::optional<std::shared_ptr<modelgbp::gbp::FloodDomain>> + flood_domain = runtime.policy_manager().getFDForGroup(uri); + + if (flood_domain) + { + if (flood_domain.get()->isUnknownFloodModeSet()) + { + if (modelgbp::gbp::UnknownFloodModeEnumT:: + CONST_HWPROXY == + flood_domain.get()->getUnknownFloodMode( + /* flood */ 1)) + { + vt_mac = spine_proxy->mk_mac(key, bd_vnid.get()); + } + else if (modelgbp::gbp::UnknownFloodModeEnumT:: + CONST_DROP == + flood_domain.get()->getUnknownFloodMode( + /* flood */ 1)) + { + gbd_flags |= + gbp_bridge_domain::flags_t::UU_FWD_DROP; + } + } + if (flood_domain.get()->isBcastFloodModeSet()) + { + if (modelgbp::gbp::BcastFloodModeEnumT::CONST_DROP == + flood_domain.get()->getBcastFloodMode( + /* normal */ 0)) + { + gbd_flags |= gbp_bridge_domain::flags_t::MCAST_DROP; + } + } + if (flood_domain.get()->isEpLearnModeSet()) + { + if (modelgbp::gbp::EpLearningModeEnumT:: + CONST_DISABLED == + flood_domain.get()->getEpLearnMode(/* enabled */ 1)) + { + gbd_flags |= + gbp_bridge_domain::flags_t::DO_NOT_LEARN; + } + } + if (flood_domain.get()->isArpModeSet()) + { + if (modelgbp::gbp::AddressResModeEnumT::CONST_UNICAST == + flood_domain.get()->getArpMode(/* flood */ 1)) + { + gbd_flags |= gbp_bridge_domain::flags_t::UCAST_ARP; + if (!vt_mac) + vt_mac = + spine_proxy->mk_mac(key, bd_vnid.get()); + } + } + } + + vt_mc = mk_mcast_tunnel( + runtime, key, bd_vnid.get(), bd_mcast.get()); + l2_binding l2_vxbd(*vt_mc, bd); + OM::write(key, l2_vxbd); + + std::shared_ptr<VOM::gbp_route_domain> grd = + mk_gbp_rd(runtime, key, rd, rd_vnid.get()); + + gbp_vxlan gvx_rd(rd_vnid.get(), + *grd, + runtime.uplink.local_address().to_v4()); + OM::write(key, gvx_rd); + + /* + * Add the base GBP-vxlan tunnels that will be used to derive + * the learned endpoints + */ + + /* + * construct a BD that uses the MAC spine proxy as the + * UU-fwd interface + */ + gbp_bridge_domain gbd(bd, bvi, vt_mac, vt_mc, gbd_flags); + OM::write(key, gbd); + + /* + * base tunnel on which the TEPs derive and EPs are learnt + */ + gbp_vxlan gvx_bd( + bd_vnid.get(), gbd, runtime.uplink.local_address().to_v4()); + OM::write(key, gvx_bd); + + gepg = std::make_shared<gbp_endpoint_group>( + fwd.vnid, fwd.sclass, *grd, gbd); + } + else + { + VLOGE << "no RD/BD vnid or sclass " << uri; + } + } + else + { + /* + * STITCHED MODE + * + * make the VLAN based uplink interface for the group + */ + std::shared_ptr<interface> encap_link = + runtime.uplink.mk_interface(key, fwd.vnid); + + /* + * Add the encap-link to the BD + * + * If the encap link is a VLAN, then set the pop VTR operation on + * the + * link so that the VLAN tag is correctly pop/pushed on rx/tx resp. + */ + l2_binding l2_upl(*encap_link, bd); + if (interface::type_t::VXLAN != encap_link->type()) + { + l2_upl.set(l2_vtr::option_t::POP_1, fwd.vnid); + } + OM::write(key, l2_upl); + + gbp_bridge_domain gbd(bd, *bvi); + OM::write(key, gbd); + + gbp_route_domain grd(rd); + OM::write(key, grd); + + gepg = std::make_shared<gbp_endpoint_group>( + fwd.vnid, fwd.sclass, *encap_link, grd, gbd); + } + /* + * GBP Endpoint Group + */ + gepg->set(retention); + OM::write(key, *gepg); + } + catch (EndPointGroupManager::NoFowardInfoException &nofwd) + { + VLOGD << "NOT Updating endpoint-group: " << nofwd.reason << " : " + << uri; + } + + return gepg; +} + +void +EndPointGroupManager::handle_update(const opflex::modb::URI &epgURI) +{ + const std::string &epg_uuid = epgURI.toString(); + + /* + * Mark all of this EPG's state stale. this RAII pattern + * will sweep all state that is not updated. + */ + OM::mark_n_sweep ms(epg_uuid); + + VLOGD << "Updating endpoint-group:" << epgURI; + + opflexagent::PolicyManager &pm = m_runtime.policy_manager(); + + if (!pm.groupExists(epgURI)) + { + VLOGD << "Deleting endpoint-group:" << epgURI; + return; + } + + std::shared_ptr<VOM::gbp_endpoint_group> gepg = + mk_group(m_runtime, epg_uuid, epgURI); + + if (gepg) + { + std::shared_ptr<interface> bvi = gepg->get_bridge_domain()->get_bvi(); + std::shared_ptr<bridge_domain> bd = + gepg->get_bridge_domain()->get_bridge_domain(); + std::shared_ptr<route_domain> rd = + gepg->get_route_domain()->get_route_domain(); + + /* + * The BVI is the NAT inside interface for the VMs + */ + nat_binding nb6(*bvi, + direction_t::INPUT, + l3_proto_t::IPV6, + nat_binding::zone_t::INSIDE); + nat_binding nb4(*bvi, + direction_t::INPUT, + l3_proto_t::IPV4, + nat_binding::zone_t::INSIDE); + OM::write(epg_uuid, nb4); + OM::write(epg_uuid, nb6); + + /* + * For each subnet the EPG has + */ + opflexagent::PolicyManager::subnet_vector_t subnets; + pm.getSubnetsForGroup(epgURI, subnets); + + for (auto sn : subnets) + { + boost::optional<boost::asio::ip::address> routerIp = + opflexagent::PolicyManager::getRouterIpForSubnet(*sn); + + if (!sn->getPrefixLen() || !sn->getAddress()) continue; + + if (routerIp) + { + boost::asio::ip::address raddr = routerIp.get(); + /* + * - apply the host prefix on the BVI + * - add an entry into the ARP Table for it. + */ + l3_binding l3(*bvi, {raddr}); + OM::write(epg_uuid, l3); + + bridge_domain_arp_entry bae( + *bd, raddr, bvi->l2_address().to_mac()); + OM::write(epg_uuid, bae); + } + /* + * The subnet is an internal 'GBP subnet' i.e. it is one where + * the egress the is the EPG's uplink. And the EPG is chosen + * based on the packet's source port + */ + route::prefix_t pfx(sn->getAddress().get(), + sn->getPrefixLen().get()); + + gbp_subnet gs(*rd, + pfx.low(), + (gepg->get_route_domain()->get_ip4_uu_fwd() + ? gbp_subnet::type_t::TRANSPORT + : gbp_subnet::type_t::STITCHED_INTERNAL)); + OM::write(epg_uuid, gs); + } + } +} + +}; // namespace VPP + +/* + * Local Variables: + * eval: (c-set-style "llvm.org") + * End: + */ |