/* -*- 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