From 08dfca551faf420f2c15738ebfb4f26a6c875a51 Mon Sep 17 00:00:00 2001 From: Praveen K V Date: Sat, 24 Dec 2016 12:47:44 +0530 Subject: [PATCH] Ensure flow-stickiness in case of ECMP with bridged forward flow and routed reverse flow Commit 14333196c7fe7f2351a9552521719f979373c102 supported stickiness across bridge and routed flows. However, it doenst support multiple ECMP members on same compute node. This current supports multiple members of ECMP in same compute When layer-3 flow is created with local-ecmp-nh as key, look for layer-2 flow with one of the local-ecmp members as key. If layer-2 flow with interface-nh is present, stitch layer-3 and layer-2 flows Conflicts: src/vnsw/agent/pkt/flow_entry.cc src/vnsw/agent/pkt/flow_entry.h src/vnsw/agent/pkt/test/egress-flow.xml src/vnsw/agent/pkt/test/test_pkt_util.cc src/vnsw/agent/pkt/test/test_pkt_util.h Change-Id: I3fd0185fb9856fbcbdc2b038a75fa65d54183802 closes-Bug: #1648696 --- src/vnsw/agent/pkt/flow_entry.cc | 24 +- src/vnsw/agent/pkt/flow_entry.h | 9 +- src/vnsw/agent/pkt/pkt_flow_info.cc | 191 +++- src/vnsw/agent/pkt/pkt_flow_info.h | 1 + src/vnsw/agent/pkt/test/SConscript | 2 + src/vnsw/agent/pkt/test/egress-flow.xml | 8 +- src/vnsw/agent/pkt/test/l2-sg-flow.xml | 12 +- src/vnsw/agent/pkt/test/test_ecmp.cc | 76 -- .../agent/pkt/test/test_ecmp_bridge_route.cc | 873 ++++++++++++++++++ .../agent/pkt/test/test_flow_trace_filter.cc | 15 - src/vnsw/agent/pkt/test/test_flowtable.cc | 6 +- .../agent/pkt/test/test_pkt_flow_limits.cc | 16 - src/vnsw/agent/pkt/test/test_pkt_util.cc | 16 + src/vnsw/agent/pkt/test/test_pkt_util.h | 2 + src/vnsw/agent/test/pkt_gen.h | 2 - src/vnsw/agent/uve/test/test_port_bitmap.cc | 3 +- 16 files changed, 1088 insertions(+), 168 deletions(-) create mode 100644 src/vnsw/agent/pkt/test/test_ecmp_bridge_route.cc diff --git a/src/vnsw/agent/pkt/flow_entry.cc b/src/vnsw/agent/pkt/flow_entry.cc index 238e2b243d9..16a3afc0b0c 100644 --- a/src/vnsw/agent/pkt/flow_entry.cc +++ b/src/vnsw/agent/pkt/flow_entry.cc @@ -83,6 +83,7 @@ const std::map ((uint16_t)SHORT_FLOW_ON_TSN, "Short flow TSN flow") ((uint16_t)SHORT_NO_MIRROR_ENTRY, "Short flow No mirror entry ") ((uint16_t)SHORT_SAME_FLOW_RFLOW_KEY,"Short flow same flow and rflow") + ((uint16_t)SHORT_INACTIVE_NH, "Shot flow - Inactive Nexthop") ((uint16_t)DROP_POLICY, "Flow drop Policy") ((uint16_t)DROP_OUT_POLICY, "Flow drop Out Policy") ((uint16_t)DROP_SG, "Flow drop SG") @@ -415,6 +416,12 @@ void FlowEntry::Reset(const FlowKey &k) { key_ = k; } +// Change key for a flow-table. Caller must ensure that flow-entry is not +// in tree +void FlowEntry::SetKey(const FlowKey &k) { + key_ = k; +} + void FlowEntry::Init() { alloc_count_ = 0; } @@ -500,7 +507,7 @@ void intrusive_ptr_release(FlowEntry *fe) { bool FlowEntry::InitFlowCmn(const PktFlowInfo *info, const PktControlInfo *ctrl, const PktControlInfo *rev_ctrl, - FlowEntry *rflow) { + FlowEntry *rflow, bool l3_flow) { reverse_flow_entry_ = rflow; reset_flags(FlowEntry::ReverseFlow); peer_vrouter_ = info->peer_vrouter; @@ -552,7 +559,7 @@ bool FlowEntry::InitFlowCmn(const PktFlowInfo *info, const PktControlInfo *ctrl, data_.vn_entry = ctrl->vn_ ? ctrl->vn_ : rev_ctrl->vn_; data_.in_vm_entry.SetVm(ctrl->vm_); data_.out_vm_entry.SetVm(rev_ctrl->vm_); - l3_flow_ = info->l3_flow; + l3_flow_ = l3_flow; data_.ecmp_rpf_nh_ = 0; data_.acl_assigned_vrf_index_ = VrfEntry::kInvalidIndex; return true; @@ -561,10 +568,10 @@ bool FlowEntry::InitFlowCmn(const PktFlowInfo *info, const PktControlInfo *ctrl, void FlowEntry::InitFwdFlow(const PktFlowInfo *info, const PktInfo *pkt, const PktControlInfo *ctrl, const PktControlInfo *rev_ctrl, - FlowEntry *rflow, Agent *agent) { + FlowEntry *rflow, Agent *agent, bool l3_flow) { gen_id_ = pkt->GetAgentHdr().cmd_param_5; flow_handle_ = pkt->GetAgentHdr().cmd_param; - if (InitFlowCmn(info, ctrl, rev_ctrl, rflow) == false) { + if (InitFlowCmn(info, ctrl, rev_ctrl, rflow, l3_flow) == false) { return; } if (info->linklocal_bind_local_port) { @@ -634,9 +641,9 @@ void FlowEntry::InitFwdFlow(const PktFlowInfo *info, const PktInfo *pkt, void FlowEntry::InitRevFlow(const PktFlowInfo *info, const PktInfo *pkt, const PktControlInfo *ctrl, const PktControlInfo *rev_ctrl, - FlowEntry *rflow, Agent *agent) { + FlowEntry *rflow, Agent *agent, bool l3_flow) { uint32_t intf_in; - if (InitFlowCmn(info, ctrl, rev_ctrl, rflow) == false) { + if (InitFlowCmn(info, ctrl, rev_ctrl, rflow, l3_flow) == false) { return; } set_flags(FlowEntry::ReverseFlow); @@ -1166,6 +1173,11 @@ void FlowEntry::GetVrfAssignAcl() { return; } + // Skip VrfTranslate rules for l2-flows + if (l3_flow() == false) { + return; + } + if (data_.intf_entry->type() != Interface::VM_INTERFACE) { return; } diff --git a/src/vnsw/agent/pkt/flow_entry.h b/src/vnsw/agent/pkt/flow_entry.h index e8a24b79635..ee280a44d4f 100644 --- a/src/vnsw/agent/pkt/flow_entry.h +++ b/src/vnsw/agent/pkt/flow_entry.h @@ -422,6 +422,7 @@ class FlowEntry { SHORT_NO_MIRROR_ENTRY, SHORT_SAME_FLOW_RFLOW_KEY, SHORT_NO_SRC_ROUTE_L2RPF, + SHORT_INACTIVE_NH, SHORT_MAX }; @@ -485,6 +486,7 @@ class FlowEntry { void Reset(const FlowKey &k); void Reset(); + void SetKey(const FlowKey &k); // Copy data fields from rhs void Copy(FlowEntry *rhs, bool update); @@ -492,11 +494,11 @@ class FlowEntry { void InitFwdFlow(const PktFlowInfo *info, const PktInfo *pkt, const PktControlInfo *ctrl, const PktControlInfo *rev_ctrl, FlowEntry *rflow, - Agent *agent); + Agent *agent, bool l3_flow); void InitRevFlow(const PktFlowInfo *info, const PktInfo *pkt, const PktControlInfo *ctrl, const PktControlInfo *rev_ctrl, FlowEntry *rflow, - Agent *agent); + Agent *agent, bool l3_flow); void InitAuditFlow(uint32_t flow_idx, uint8_t gen_id); static void Init(); @@ -670,7 +672,8 @@ class FlowEntry { bool SetEcmpRpfNH(FlowTable*, uint32_t); bool SetRpfNHState(FlowTable*, const NextHop*); bool InitFlowCmn(const PktFlowInfo *info, const PktControlInfo *ctrl, - const PktControlInfo *rev_ctrl, FlowEntry *rflow); + const PktControlInfo *rev_ctrl, FlowEntry *rflow, + bool l3_flow); void GetSourceRouteInfo(const AgentRoute *rt); void GetDestRouteInfo(const AgentRoute *rt); void UpdateRpf(); diff --git a/src/vnsw/agent/pkt/pkt_flow_info.cc b/src/vnsw/agent/pkt/pkt_flow_info.cc index 2318aff8eca..de5cb598128 100644 --- a/src/vnsw/agent/pkt/pkt_flow_info.cc +++ b/src/vnsw/agent/pkt/pkt_flow_info.cc @@ -286,40 +286,44 @@ static bool NhDecode(const NextHop *nh, const PktInfo *pkt, PktFlowInfo *info, // have MPLS label. The MPLS label can point to // 1. In case of non-ECMP, label will points to local interface // 2. In case of ECMP, label will point to ECMP of local-composite members + // Setup the NH for reverse flow appropriately case NextHop::TUNNEL: { - if (pkt->l3_forwarding) { - const InetUnicastRouteEntry *rt = - static_cast(in->rt_); - if (rt != NULL && rt->GetLocalNextHop()) { - const NextHop *local_nh = rt->GetLocalNextHop(); - out->nh_ = local_nh->id(); - if (local_nh->GetType() == NextHop::INTERFACE) { - const Interface *local_intf = - static_cast(local_nh)->GetInterface(); - //Get policy enabled nexthop only for - //vm interface, in case of vgw or service interface in - //transparent mode we should still - //use policy disabled interface - if (local_intf && - local_intf->type() == Interface::VM_INTERFACE) { - if (local_nh->IsActive()) { - out->nh_ = local_intf->flow_key_nh()->id(); - } else { - LogError(pkt, "Invalid or Inactive ifindex"); - info->short_flow = true; - info->short_flow_reason = - FlowEntry::SHORT_UNAVIALABLE_INTERFACE; - } - } - } - } else { - out->nh_ = in->nh_; - } - } else { - // Bridged flow. ECMP not supported for L2 flows - out->nh_ = in->nh_; - } + // out->intf_ is invalid for packets going out on tunnel. Reset it. out->intf_ = NULL; + + // Packet going out on tunnel. Assume NH in reverse flow is same as + // that of forward flow. It can be over-written down if route for + // source-ip is ECMP + out->nh_ = in->nh_; + + // The NH in reverse flow can change only if ECMP-NH is used. There is + // no ECMP for layer2 flows + if (pkt->l3_forwarding == false) { + break; + } + + // If source-ip is in ECMP, reverse flow would use ECMP-NH as key + const InetUnicastRouteEntry *rt = + dynamic_cast(in->rt_); + if (rt == NULL) { + break; + } + + // Get only local-NH from route + const NextHop *local_nh = rt->GetLocalNextHop(); + if (local_nh->IsActive() == false) { + LogError(pkt, "Invalid or Inactive local nexthop "); + info->short_flow = true; + info->short_flow_reason = FlowEntry::SHORT_UNAVIALABLE_INTERFACE; + break; + } + + // Change NH in reverse flow if route points to composite-NH + const CompositeNH *comp_nh = dynamic_cast + (local_nh); + if (comp_nh != NULL) { + out->nh_ = comp_nh->id(); + } break; } @@ -1054,6 +1058,10 @@ void PktFlowInfo::FloatingIpSNat(const PktInfo *pkt, PktControlInfo *in, bool PktFlowInfo::VrfTranslate(const PktInfo *pkt, PktControlInfo *in, PktControlInfo *out, const IpAddress &src_ip, bool nat_flow) { + // Skip VrfTranslate rules for l2-flows + if (l3_flow == false) + return true; + const Interface *intf = NULL; if (ingress) { intf = in->intf_; @@ -1699,6 +1707,105 @@ static bool ShouldSwapFlows(const PktFlowInfo *info, const PktInfo *pkt, return false; } +// We want to support a scenario where layer-2 flow is created for forward +// flow and reverse packet is received as layer-3 packet. In this case, we +// want to stitch the flows created for layer-2 and layer-3 flows +// +// While setting up layer-3 flow check if there was a layer-2 flow created for +// same session. The checks to find layer-2 flow depends on type of flow. +// Note, the layer-2 flow would always be created with interface-nh as key +// +// Notations: +// ---------- +// Layer-2 flows are denoted as L2-Fwd-Flow and L2-Rev-Flow +// Layer-2 flows are denoted as L3-Fwd-Flow and L3-Rev-Flow +// +// Local Flow: +// ----------- +// Both L2 and L3 Flows are created with interface-nh in key even if source or +// destination is ECMP. +// +// FlowTable::Add ensures layer-2 flows is re-used since keys match +// +// Egress Flow: +// ------------ +// Layer-2 flow would be creaed with interface-nh as key +// Reverse flow *will* be created with interface-nh as key +// +// FlowTable::Add ensures layer-2 flows is re-used since keys match +// +// Ingress Flow: +// ------------ +// If L3-Fwd-Flow is created with interface-nh +// Key for L3-Fwd-Flow matches key for L2-Rev-Flow +// Stitch L3-Fwd-Flow and L2-Fwd-Flow +// FlowTable::Add ensures layer-2 flows is re-used since keys match +// +// If L3-Fwd-Flow is created with Ecmp-nh +// Nexthop in L2-Fwd-Flow would be a member in Ecmp-NH +// Iterate thru all local interface-nh in Ecmp-NH +// Find L2-Fwd-Flow with interface-nh as 5-tuple in L3-Rev-Flow as key +// If flow is found +// Stitch L3-Fwd-Flow and L2-Fwd-Flow +static bool StitchL2Flow(const Agent *agent, const PktFlowInfo *info, + const PktInfo *pkt, FlowEntryPtr &flow, + FlowEntryPtr &rflow) { + if (info->short_flow) { + return false; + } + + // If this is message processing, then retain forward and reverse flows + if (pkt->type == PktType::MESSAGE) { + return false; + } + + FlowTable *flow_table = info->get_flow_table(); + NextHopTable *nh_table = agent->nexthop_table(); + const NextHop *nh = nh_table->FindNextHop(rflow->key().nh); + // If reverse flow has interface-nh as key, find L2-Fwd-Flow with + // interface-nh and 5-tuple as key + if (dynamic_cast(nh)) { + return false; + } + + // When reverse flow uses interface-nh, the keys for layer-2 and layer-3 + // flow will match and FlowTable::AddInternal will stitch them. + // + // If NH is composite-NH, look for flow interface-nh in composite and find + // layer-2 flows using the interface-nh. + if (const CompositeNH *comp_nh = dynamic_cast(nh)) { + FlowKey key = rflow->key(); + // Iterate thru all local members + ComponentNHList::const_iterator it = comp_nh->begin(); + while (it != comp_nh->end()) { + const InterfaceNH *intf_nh = + dynamic_cast(it->get()->nh()); + it++; + if (intf_nh == NULL) + continue; + + const VmInterface *vmi = + dynamic_cast(intf_nh->GetInterface()); + if (vmi == NULL) + continue; + + intf_nh = dynamic_cast(vmi->flow_key_nh()); + if (intf_nh == NULL) + continue; + + key.nh = intf_nh->id(); + FlowEntry *l2_fwd_flow = flow_table->Find(key); + if (l2_fwd_flow == NULL) { + continue; + } + rflow->SetKey(l2_fwd_flow->key()); + return true; + } + } + + return false; +} + void PktFlowInfo::Add(const PktInfo *pkt, PktControlInfo *in, PktControlInfo *out) { bool update = false; @@ -1756,6 +1863,7 @@ void PktFlowInfo::Add(const PktInfo *pkt, PktControlInfo *in, rflow = FlowEntry::Allocate(rkey, flow_table); } + // Should we swap forward/reverse flows? bool swap_flows = false; // If this is message processing or ECMP resolution, then retain forward // and reverse flows @@ -1766,10 +1874,19 @@ void PktFlowInfo::Add(const PktInfo *pkt, PktControlInfo *in, swap_flows = true; } + // It is possible that we already have a L2 flow with same 5-tuple. Stich + // current flow with old L2 flow in that case + bool rflow_l3_flow = l3_flow; + if (StitchL2Flow(agent, this, pkt, flow, rflow)) { + swap_flows = true; + rflow_l3_flow = false; + } + tcp_ack = pkt->tcp_ack; - flow->InitFwdFlow(this, pkt, in, out, rflow.get(), agent); + flow->InitFwdFlow(this, pkt, in, out, rflow.get(), agent, l3_flow); if (rflow != NULL) { - rflow->InitRevFlow(this, pkt, out, in, flow.get(), agent); + rflow->InitRevFlow(this, pkt, out, in, flow.get(), agent, + rflow_l3_flow); } flow->GetPolicyInfo(); @@ -1876,8 +1993,8 @@ void PktFlowInfo::UpdateFipStatsInfo void PktFlowInfo::GetEcmpCompositeAffinityNh() { // Pick the first member in ECMP by default out_component_nh_idx = 0; - if (flow_entry->IsForwardFlow()) - return; + //if (flow_entry->IsForwardFlow()) + // return; // Get reverse-flow. We will try to setup ECMP member such that packet is // forwarded to origin of reverse flow @@ -1931,7 +2048,7 @@ void PktFlowInfo::RewritePktInfo(uint32_t flow_index) { flow_entry = flow_table->Find(key); if (!flow_entry) { std::ostringstream ostr; - ostr << "ECMP Resolve: unable to find flow index " << flow_index; + ostr << "ECMP Resolve: Flow not present in table " << flow_index; PKTFLOW_TRACE(Err,ostr.str()); return; } diff --git a/src/vnsw/agent/pkt/pkt_flow_info.h b/src/vnsw/agent/pkt/pkt_flow_info.h index c9455e56c43..bf5dcc780a9 100644 --- a/src/vnsw/agent/pkt/pkt_flow_info.h +++ b/src/vnsw/agent/pkt/pkt_flow_info.h @@ -57,6 +57,7 @@ class PktFlowInfo { alias_ip_flow(false), ttl(0), ecmp_component_affinity_nh(NULL) { } + FlowTable *get_flow_table() const { return flow_table; } static bool ComputeDirection(const Interface *intf); void CheckLinkLocal(const PktInfo *pkt); void LinkLocalServiceFromVm(const PktInfo *pkt, PktControlInfo *in, diff --git a/src/vnsw/agent/pkt/test/SConscript b/src/vnsw/agent/pkt/test/SConscript index a9a91ca83f4..49129762a7a 100644 --- a/src/vnsw/agent/pkt/test/SConscript +++ b/src/vnsw/agent/pkt/test/SConscript @@ -47,6 +47,8 @@ test_pkt_parse = AgentEnv.MakeTestCmd(env, 'test_pkt_parse', pkt_flaky_test_suit test_flowtable = AgentEnv.MakeTestCmd(env, 'test_flowtable', pkt_test_suite) test_pkt_fip = AgentEnv.MakeTestCmd(env, 'test_pkt_fip', pkt_flaky_test_suite) test_ecmp = AgentEnv.MakeTestCmd(env, 'test_ecmp', pkt_flaky_test_suite) +test_ecmp_bridge_route = AgentEnv.MakeTestCmd(env, 'test_ecmp_bridge_route', + pkt_test_suite) test_flow_scale = AgentEnv.MakeTestCmd(env, 'test_flow_scale', pkt_flaky_test_suite) test_flow_freelist = AgentEnv.MakeTestCmd(env, 'test_flow_freelist', pkt_test_suite) test_sg_flow = AgentEnv.MakeTestCmd(env, 'test_sg_flow', pkt_flaky_test_suite) diff --git a/src/vnsw/agent/pkt/test/egress-flow.xml b/src/vnsw/agent/pkt/test/egress-flow.xml index 5a14e486427..4ff788c2079 100644 --- a/src/vnsw/agent/pkt/test/egress-flow.xml +++ b/src/vnsw/agent/pkt/test/egress-flow.xml @@ -38,7 +38,7 @@ - - - - diff --git a/src/vnsw/agent/pkt/test/l2-sg-flow.xml b/src/vnsw/agent/pkt/test/l2-sg-flow.xml index d153ef6a116..22c8eed34bd 100644 --- a/src/vnsw/agent/pkt/test/l2-sg-flow.xml +++ b/src/vnsw/agent/pkt/test/l2-sg-flow.xml @@ -60,10 +60,10 @@ - - @@ -75,10 +75,10 @@ - - @@ -97,10 +97,10 @@ - - diff --git a/src/vnsw/agent/pkt/test/test_ecmp.cc b/src/vnsw/agent/pkt/test/test_ecmp.cc index d0cc8b40fb2..9ecf024ba62 100644 --- a/src/vnsw/agent/pkt/test/test_ecmp.cc +++ b/src/vnsw/agent/pkt/test/test_ecmp.cc @@ -2444,82 +2444,6 @@ TEST_F(EcmpTest, FlowDir_Fwd_Rev_1) { DeleteRemoteRoute("vrf2", "20.1.1.0", 24); } -// Source IP is in ECMP -// Destination IP is non-ECMP -// Destination receives a L2 Flow -// Destination replies with L3 Flow -// The old-reverse flow must be stitched to use right ECMP index so that it -// reaches right compute-node -// -// FIXME : The test is not complete. For packet trapped with ECMP_RESOLVE, -// flow code tried to get flow-key from mapped flow-memory. -// This API failsin UT. So, the test is not really complete -TEST_F(EcmpTest, L2ToL3_1) { - uint8_t nh_count = 4; - // Create an ECMP flow from fabric - ComponentNHKeyList local_comp_nh; - AddRemoteEcmpRoute("vrf2", "20.1.1.0", 24, "vn2", nh_count, local_comp_nh); - client->WaitForIdle(); - - int remote_server_ip = 0x0A0A0A0A; - Ip4Address ip_list[nh_count]; - MacAddress mac_list[nh_count]; - - for (uint32_t i = 0; i < nh_count; i++) { - ip_list[i] = Ip4Address(remote_server_ip + i); - mac_list[i] = MacAddress(0x0, 0x0, 0x0, 0x10, 0x0, i); - AddRemoteEvpnVmRoute("vrf2", mac_list[i].ToString().c_str(), 100+i, - "vn2", ip_list[0].to_string().c_str()); - } - client->WaitForIdle(); - - uint32_t flow_nh_count[nh_count]; - for (uint32_t i = 0; i < nh_count; i++) { - flow_nh_count[i] = 0; - } - - // Add flows from each of the tunnel source and validate that flow - // stickiness is maintained even in case of ECMP revaluation - for (uint32_t i = 0; i < nh_count; i++) { - VmInterface *vmi = static_cast(VmPortGet(1)); - Ip4Address dest = Ip4Address(0x14010101 + i); - TxL2IpMplsPacket(eth_intf_id_, ip_list[i].to_string().c_str(), - agent_->router_id().to_string().c_str(), - vmi->l2_label(), mac_list[i].ToString().c_str(), - vmi->vm_mac().ToString().c_str(), - dest.to_string().c_str(), "1.1.1.1", 1); - client->WaitForIdle(); - - FlowEntry *entry = FlowGet(VrfGet("vrf2")->vrf_id(), - dest.to_string().c_str(), "1.1.1.1", 1, 0, 0, - GetFlowKeyNH(1)); - EXPECT_TRUE(entry != NULL); - EXPECT_TRUE(entry->data().component_nh_idx = - CompositeNH::kInvalidComponentNHIdx); - - // Reverse flow is no ECMP - FlowEntry *rev_entry = entry->reverse_flow_entry(); - EXPECT_TRUE(rev_entry->data().component_nh_idx == - CompositeNH::kInvalidComponentNHIdx); - - TxIpPacketEcmp(VmPortGetId(1), "1.1.1.1", dest.to_string().c_str(), 1); - client->WaitForIdle(); - - EXPECT_TRUE(rev_entry->data().component_nh_idx != - CompositeNH::kInvalidComponentNHIdx); - flow_nh_count[rev_entry->data().component_nh_idx]++; - } - - for (uint32_t i = 0; i < nh_count; i++) { - EXPECT_EQ(1, flow_nh_count[i]); - } - // Cleanup - DeleteRemoteRoute("vrf2", "20.1.1.0", 24); - for (uint32_t i = 0; i < nh_count; i++) { - DeleteRemoteEvpnRoute("vrf2", mac_list[i].ToString().c_str()); - } -} - int main(int argc, char *argv[]) { GETUSERARGS(); client = TestInit(init_file, ksync_init, true, true, true, 100*1000); diff --git a/src/vnsw/agent/pkt/test/test_ecmp_bridge_route.cc b/src/vnsw/agent/pkt/test/test_ecmp_bridge_route.cc new file mode 100644 index 00000000000..d1a5b9a9933 --- /dev/null +++ b/src/vnsw/agent/pkt/test/test_ecmp_bridge_route.cc @@ -0,0 +1,873 @@ +/* + * Copyright (c) 2013 Juniper Networks, Inc. All rights reserved. + */ + +#include "base/os.h" +#include "test/test_cmn_util.h" +#include "test_pkt_util.h" +#include "pkt/flow_proto.h" +#include "oper/tunnel_nh.h" + +#define COMPARE_TRUE(expr) \ + do {\ + EXPECT_TRUE((expr));\ + if ((expr) != true) ret = false;\ + } while(0) + +#define COMPARE_FALSE(expr) \ + do {\ + EXPECT_FALSE((expr));\ + if ((expr) != false) ret = false;\ + } while(0) + +#define COMPARE_EQ(v1, v2) \ + do {\ + EXPECT_EQ((v1), (v2));\ + if ((v1) != (v2)) ret = false;\ + } while(0) + +#define COMPARE_NE(v1, v2) \ + do {\ + EXPECT_NE((v1, v2));\ + if ((v1) == (v2)) ret = false;\ + } while(0) + +#define LOCAL_ECMP_IP_1 "1.1.1.3" +#define LOCAL_ECMP_IP_2 "1.1.1.4" +#define LOCAL_NON_ECMP_IP_3 "1.1.1.5" +#define LOCAL_NON_ECMP_IP_4 "1.1.1.6" +#define REMOTE_NON_ECMP_IP_1 "1.1.1.7" +#define REMOTE_ECMP_IP_1 "1.1.1.8" +#define REMOTE_SERVER_1 "100.100.100.1" +#define REMOTE_SERVER_2 "100.100.100.2" +#define REMOTE_SERVER_3 "100.100.100.3" +#define REMOTE_SERVER_4 "100.100.100.4" + +#define LOCAL_MAC_1 "00:00:00:01:01:01" +#define LOCAL_MAC_2 "00:00:00:01:01:02" +#define LOCAL_MAC_3 "00:00:00:01:01:03" +#define LOCAL_MAC_4 "00:00:00:01:01:04" +#define LOCAL_MAC_5 "00:00:00:01:01:05" +#define LOCAL_MAC_6 "00:00:00:01:01:06" +#define LOCAL_MAC_7 "00:00:00:01:01:07" +#define LOCAL_MAC_8 "00:00:00:01:01:08" +#define LOCAL_MAC_9 "00:00:00:01:01:09" +#define LOCAL_MAC_10 "00:00:00:01:01:10" + +#define REMOTE_MAC_1 "00:00:00:02:01:01" +#define REMOTE_MAC_2 "00:00:00:02:01:02" +#define REMOTE_MAC_3 "00:00:00:02:01:03" +#define REMOTE_MAC_4 "00:00:00:02:01:04" +#define REMOTE_MAC_5 "00:00:00:02:01:05" + +struct PortInfo input1[] = { + {"vnet1", 1, LOCAL_ECMP_IP_1, LOCAL_MAC_1, 1, 1}, + {"vnet2", 2, LOCAL_ECMP_IP_1, LOCAL_MAC_2, 1, 2}, + {"vnet3", 3, LOCAL_ECMP_IP_1, LOCAL_MAC_3, 1, 3}, + {"vnet4", 4, LOCAL_ECMP_IP_1, LOCAL_MAC_4, 1, 4}, +}; + +struct PortInfo input2[] = { + {"vnet5", 5, LOCAL_ECMP_IP_2, LOCAL_MAC_5, 1, 5}, + {"vnet6", 6, LOCAL_ECMP_IP_2, LOCAL_MAC_6, 1, 6}, + {"vnet7", 7, LOCAL_ECMP_IP_2, LOCAL_MAC_7, 1, 7}, + {"vnet8", 8, LOCAL_ECMP_IP_2, LOCAL_MAC_8, 1, 8}, +}; + +struct PortInfo input3[] = { + {"vnet9", 9, LOCAL_NON_ECMP_IP_3, LOCAL_MAC_9, 1, 9}, +}; + +struct PortInfo input4[] = { + {"vnet10", 10, LOCAL_NON_ECMP_IP_4, LOCAL_MAC_10, 1, 10}, +}; + +IpamInfo ipam_info[] = { + {"1.1.1.0", 24, "1.1.1.1"}, +}; + +class EcmpTest : public ::testing::Test { +public: + virtual void SetUp() { + agent_ = Agent::GetInstance(); + flow_proto_ = agent_->pkt()->get_flow_proto(); + eth_intf_id_ = EthInterfaceGet("vnet0")->id(); + strcpy(router_id_, agent_->router_id().to_string().c_str()); + + strcpy(remote_server_ip_[0], REMOTE_SERVER_1); + strcpy(remote_server_ip_[1], REMOTE_SERVER_2); + strcpy(remote_server_ip_[2], REMOTE_SERVER_3); + strcpy(remote_server_ip_[3], REMOTE_SERVER_4); + strcpy(remote_mac_[0], REMOTE_MAC_1); + strcpy(remote_mac_[1], REMOTE_MAC_2); + strcpy(remote_mac_[2], REMOTE_MAC_3); + strcpy(remote_mac_[3], REMOTE_MAC_4); + + AddIPAM("vn1", ipam_info, 1); + client->WaitForIdle(); + + /****************************************************************** + * Create following interfaces + * vnet1, vnet2, vnet3, vnet4 in ECMP + * vnet5, vnet6, vnet7, vnet8 in ECMP + *****************************************************************/ + CreateVmportWithEcmp(input1, 4); + CreateVmportWithEcmp(input2, 4); + CreateVmportEnv(input3, 1); + CreateVmportEnv(input4, 1); + client->WaitForIdle(); + for (uint32_t i = 1; i < 9; i++) { + EXPECT_TRUE(VmPortActive(i)); + } + + vrf_id1_ = VrfGet("vrf1")->vrf_id(); + rt1_ = RouteGet("vrf1", Ip4Address::from_string(LOCAL_ECMP_IP_1), 32); + ecmp_nh1_ = rt1_->GetActiveNextHop(); + ecmp_label1_ = rt1_->GetActiveLabel(); + EXPECT_TRUE(rt1_ != NULL); + + rt2_ = RouteGet("vrf1", Ip4Address::from_string(LOCAL_ECMP_IP_2), 32); + ecmp_nh2_ = rt2_->GetActiveNextHop(); + ecmp_label2_ = rt2_->GetActiveLabel(); + EXPECT_TRUE(rt2_ != NULL); + + uc_rt_table_ = agent_->vrf_table()->GetInet4UnicastRouteTable("vrf1"); + + // Add remote routes + boost::system::error_code ec; + bgp_peer_ = CreateBgpPeer(Ip4Address::from_string("0.0.0.1", ec), + "xmpp channel"); + remote_vm_ip1_ = Ip4Address::from_string(REMOTE_NON_ECMP_IP_1); + + // Add non-ecmp remote VM routes + AddRemoteVmRoute("vrf1", REMOTE_NON_ECMP_IP_1, 32, 100, "vn1", + REMOTE_SERVER_1); + AddRemoteEvpnVmRoute("vrf1", REMOTE_MAC_1, 101, "vn1", + REMOTE_SERVER_1); + client->WaitForIdle(); + + // Add ecmp remote VM routes + ComponentNHKeyList local_comp_nh; + AddRemoteEcmpRoute("vrf1", REMOTE_ECMP_IP_1, 32, 200, "vn1", 4, + local_comp_nh); + // Add non-ECMP EVPN route for all peers added above + AddRemoteEvpnVmRoute("vrf1", REMOTE_MAC_2, 201, "vn1", REMOTE_SERVER_1); + AddRemoteEvpnVmRoute("vrf1", REMOTE_MAC_3, 202, "vn1", REMOTE_SERVER_2); + AddRemoteEvpnVmRoute("vrf1", REMOTE_MAC_4, 203, "vn1", REMOTE_SERVER_3); + AddRemoteEvpnVmRoute("vrf1", REMOTE_MAC_5, 204, "vn1", REMOTE_SERVER_4); + client->WaitForIdle(); + + FlowStatsTimerStartStop(agent_, true); + client->WaitForIdle(); + } + + virtual void TearDown() { + DeleteVmportEnv(input1, 4, false); + DeleteVmportEnv(input2, 4, false); + DeleteVmportEnv(input3, 1, false); + DeleteVmportEnv(input4, 1, true); + client->WaitForIdle(); + + // Delete remote routes + DeleteRemoteRoute("vrf1", REMOTE_NON_ECMP_IP_1, 32); + DeleteRemoteEvpnRoute("vrf1", REMOTE_MAC_1); + DeleteRemoteRoute("vrf1", REMOTE_ECMP_IP_1, 32); + DeleteRemoteEvpnRoute("vrf1", REMOTE_MAC_2); + DeleteRemoteEvpnRoute("vrf1", REMOTE_MAC_3); + DeleteRemoteEvpnRoute("vrf1", REMOTE_MAC_4); + DeleteRemoteEvpnRoute("vrf1", REMOTE_MAC_5); + client->WaitForIdle(); + + DelIPAM("vn1"); + client->WaitForIdle(); + + DeleteBgpPeer(bgp_peer_); + EXPECT_FALSE(VrfFind("vrf1", true)); + } + + const NextHop *GetRouteNh(const char *vrf, const char *ip, uint8_t plen) { + const InetUnicastRouteEntry *rt = + RouteGet(vrf, Ip4Address::from_string(ip), plen); + if (rt == NULL) + return NULL; + return rt->GetActiveNextHop(); + } + + void AddRemoteEcmpRoute(const char *vrf, const char *ip, uint32_t plen, + uint32_t label, const char *vn, int count, + const ComponentNHKeyList &local_list) { + Ip4Address vm_ip = Ip4Address::from_string(ip); + ComponentNHKeyList comp_nh_list = local_list; + + for(int i = 0; i < count; i++) { + Ip4Address server = Ip4Address::from_string(remote_server_ip_[i]); + ComponentNHKeyPtr comp_nh + (new ComponentNHKey(label, agent_->fabric_vrf_name(), + agent_->router_id(), server, false, + TunnelType::GREType())); + comp_nh_list.push_back(comp_nh); + } + SecurityGroupList sg_id_list; + EcmpTunnelRouteAdd(bgp_peer_, vrf, vm_ip, plen, comp_nh_list, -1, + vn, sg_id_list, PathPreference()); + } + + void AddRemoteVmRoute(const char *vrf, const char *ip, uint32_t plen, + uint32_t label, const char *vn, const char *nh_ip) { + TunnelRouteAdd(nh_ip, ip, vrf, label, vn); + } + + void AddRemoteEvpnVmRoute(const char *vrf, const char *mac, uint32_t label, + const char *vn, const char *nh_ip) { + MacAddress mac_addr = MacAddress::FromString(mac); + BridgeTunnelRouteAdd(bgp_peer_, "vrf1", TunnelType::GREType(), + Ip4Address::from_string(remote_server_ip_[0]), + label, mac_addr, Ip4Address(), 0); + } + + void DeleteRemoteRoute(const char *vrf, const char *ip, uint32_t plen) { + DeleteRoute(vrf, ip, plen, bgp_peer_); + } + + void DeleteRemoteEvpnRoute(const char *vrf, const char *mac) { + ControllerVmRoute *data = new ControllerVmRoute(bgp_peer_); + agent_->fabric_evpn_table()->DeleteReq(bgp_peer_, vrf, + MacAddress::FromString(mac), + Ip4Address(), 0, data); + } + + bool FlowTableWait(int count) { + int i = 100000; + while (i > 0) { + i--; + if (flow_proto_->FlowCount() == (size_t) count) { + return true; + } + client->WaitForIdle(); + usleep(100); + } + return (flow_proto_->FlowCount() == (size_t) count); + } + + void FlushFlowTable() { + client->WaitForIdle(); + client->EnqueueFlowFlush(); + EXPECT_TRUE(FlowTableWait(0)); + } + + bool TxL2MplsPacketFromFabric(VmInterface *vmi, const char *tunnel_sip, + uint32_t label, const char *smac, + const char *dmac, const char *sip, + const char *dip, FlowEntry **flow, + FlowEntry **rflow) { + bool ret = true; + // Generate L2 Flow + TxL2IpMplsPacket(eth_intf_id_, tunnel_sip, router_id_, + label, smac, dmac, sip, dip, 1); + client->WaitForIdle(); + + // Forward flow is not ECMP + *flow = FlowGet(vrf_id1_, sip, dip, 1, 0, 0, vmi->flow_key_nh()->id()); + COMPARE_TRUE(flow != NULL); + COMPARE_TRUE((*flow)->data().component_nh_idx == + CompositeNH::kInvalidComponentNHIdx); + + // Reverse flow is not ECMP + *rflow = (*flow)->reverse_flow_entry(); + COMPARE_TRUE((*rflow)->data().component_nh_idx == + CompositeNH::kInvalidComponentNHIdx); + return ret; + } + + bool TxL3MplsPacketFromFabric(VmInterface *vmi, const char *tunnel_sip, + uint32_t label, const char *sip, + const char *dip, FlowEntry **flow, + FlowEntry **rflow) { + bool ret = true; + // Generate L2 Flow + TxIpMplsPacket(eth_intf_id_, tunnel_sip, router_id_, + label, sip, dip, 1); + client->WaitForIdle(); + + // Forward flow is not ECMP + *flow = FlowGet(vrf_id1_, sip, dip, 1, 0, 0, vmi->flow_key_nh()->id()); + COMPARE_TRUE(flow != NULL); + COMPARE_TRUE((*flow)->data().component_nh_idx == + CompositeNH::kInvalidComponentNHIdx); + + // Reverse flow is not ECMP + *rflow = (*flow)->reverse_flow_entry(); + COMPARE_TRUE((*rflow)->data().component_nh_idx == + CompositeNH::kInvalidComponentNHIdx); + return ret; + } + + // Generate ECMP resolution event for packet fro VMI + bool EcmpResolveFromVmi(VmInterface *vmi, const char *sip, + const char *dip, const FlowEntry *flow, + const FlowEntry *rflow, uint32_t count = 2) { + bool ret = true; + // Generate ECMP resolution event + TxIpPacketEcmp(vmi->id(), sip, dip, 1, flow->flow_handle()); + client->WaitForIdle(); + + // No new flows must be generated + COMPARE_EQ(count, flow_proto_->FlowCount()); + COMPARE_FALSE(flow->l3_flow()); + COMPARE_FALSE(rflow->l3_flow()); + return ret; + } + + bool TxL2PacketFromVmi(VmInterface *vmi, const char *smac, + const char *dmac, const char *sip, + const char *dip, FlowEntry **flow, + FlowEntry **rflow) { + bool ret = true; + // Generate L2 Flow + TxL2Packet(vmi->id(), smac, dmac, sip, dip, 1); + client->WaitForIdle(); + + // Forward flow is not ECMP + *flow = FlowGet(vrf_id1_, sip, dip, 1, 0, 0, vmi->flow_key_nh()->id()); + COMPARE_TRUE(flow != NULL); + COMPARE_TRUE((*flow)->data().component_nh_idx == + CompositeNH::kInvalidComponentNHIdx); + + // Reverse flow is not ECMP + *rflow = (*flow)->reverse_flow_entry(); + COMPARE_TRUE((*rflow)->data().component_nh_idx == + CompositeNH::kInvalidComponentNHIdx); + return ret; + } + + bool TxL3PacketFromVmi(VmInterface *vmi, const char *sip, + const char *dip, FlowEntry **flow, + FlowEntry **rflow) { + bool ret = true; + // Generate L2 Flow + TxIpPacket(vmi->id(), sip, dip, 1); + client->WaitForIdle(); + + // Forward flow is not ECMP + *flow = FlowGet(vrf_id1_, sip, dip, 1, 0, 0, vmi->flow_key_nh()->id()); + COMPARE_TRUE(flow != NULL); + COMPARE_TRUE((*flow)->data().component_nh_idx == + CompositeNH::kInvalidComponentNHIdx); + + // Reverse flow is not ECMP + *rflow = (*flow)->reverse_flow_entry(); + COMPARE_TRUE((*rflow)->data().component_nh_idx == + CompositeNH::kInvalidComponentNHIdx); + return ret; + } + + // Generate ECMP resolution event for packet from fabric + bool EcmpResolveFromFabric(const char *tunnel_sip, uint32_t label, + const char *sip, const char *dip, + const FlowEntry *flow, const FlowEntry *rflow) { + bool ret = true; + // Generate ECMP resolution event + TxIpMplsPacket(eth_intf_id_, tunnel_sip, router_id_, label, + sip, dip, 1, flow->flow_handle(), true); + client->WaitForIdle(); + + // No new flows must be generated + COMPARE_EQ(2U, flow_proto_->FlowCount()); + COMPARE_FALSE(flow->l3_flow()); + COMPARE_FALSE(rflow->l3_flow()); + return ret; + } + + + // No new flows must be generated + bool ValidateEcmpAndTunnel(const FlowEntry *flow, bool ecmp, + const char *ip, const char *tunnel_ip) { + bool ret = true; + if (ecmp == false) { + COMPARE_TRUE(flow->data().component_nh_idx == + CompositeNH::kInvalidComponentNHIdx); + return ret; + } + + uint32_t ecmp_idx = flow->data().component_nh_idx; + COMPARE_TRUE(ecmp_idx != CompositeNH::kInvalidComponentNHIdx); + + // Validate the ECMP member points back to source of L2 packet + const CompositeNH *comp_nh = dynamic_cast + (GetRouteNh("vrf1", ip, 32)); + COMPARE_TRUE(comp_nh != NULL); + + const TunnelNH *tunnel_nh = + dynamic_cast(comp_nh->GetNH(ecmp_idx)); + COMPARE_TRUE(tunnel_nh != NULL); + + COMPARE_TRUE(*tunnel_nh->GetDip() == + Ip4Address::from_string(tunnel_ip)); + return ret; + } + + bool ValidateEcmpAndVmi(const FlowEntry *flow, bool ecmp, + const char *ip, const Interface *intf) { + bool ret = true; + if (ecmp == false) { + ret = (flow->data().component_nh_idx == + CompositeNH::kInvalidComponentNHIdx); + EXPECT_TRUE(ret); + return ret; + } + + uint32_t ecmp_idx = flow->data().component_nh_idx; + COMPARE_TRUE(ecmp_idx != CompositeNH::kInvalidComponentNHIdx); + + // Validate the ECMP member points back to source of L2 packet + const CompositeNH *comp_nh = dynamic_cast + (GetRouteNh("vrf1", ip, 32)); + COMPARE_TRUE(comp_nh != NULL); + + const InterfaceNH *intf_nh = + dynamic_cast(comp_nh->GetNH(ecmp_idx)); + COMPARE_TRUE(intf_nh != NULL); + + COMPARE_TRUE(intf_nh->GetInterface() == intf); + return ret; + } + +protected: + Agent *agent_; + uint32_t eth_intf_id_; + Ip4Address remote_vm_ip1_; + FlowProto *flow_proto_; + char router_id_[64]; + InetUnicastAgentRouteTable *uc_rt_table_; + uint32_t vrf_id1_; + char remote_server_ip_[4][64]; + char remote_mac_[4][64]; + + const InetUnicastRouteEntry *rt1_; + const NextHop *ecmp_nh1_; + uint32_t ecmp_label1_; + + const InetUnicastRouteEntry *rt2_; + const NextHop *ecmp_nh2_; + uint32_t ecmp_label2_; +}; + +///////////////////////////////////////////////////////////////////////////// +// Egress Flow +// Source : Non-ECMP +// Destination : Non-ECMP +///////////////////////////////////////////////////////////////////////////// +TEST_F(EcmpTest, Egress_Non_Ecmp_To_Non_Ecmp_1) { + char sip[64]; + strcpy(sip, REMOTE_NON_ECMP_IP_1); + char dip[64]; + VmInterface *vmi = static_cast(VmPortGet(9)); + strcpy(dip, vmi->primary_ip_addr().to_string().c_str()); + + FlowEntry *flow = NULL; + FlowEntry *rflow = NULL; + // Generate layer-2 packet from fabric + EXPECT_TRUE(TxL2MplsPacketFromFabric(vmi, remote_server_ip_[0], + vmi->l2_label(), REMOTE_MAC_1, + vmi->vm_mac().ToString().c_str(), + sip, dip, &flow, &rflow)); + + FlowEntry *flow_new; + FlowEntry *rflow_new; + // Generate layer-3 packet from VMI + EXPECT_TRUE(TxL3PacketFromVmi(vmi, dip, sip, &rflow_new, &flow_new)); + EXPECT_TRUE(flow_new == flow); + EXPECT_TRUE(rflow_new == rflow); + + // No new flows must be generated + EXPECT_EQ(2U, flow_proto_->FlowCount()); + // Flows should be l2_flow still + EXPECT_FALSE(flow->l3_flow()); + EXPECT_FALSE(rflow->l3_flow()); + + // ECMP must not be set on both flows + EXPECT_TRUE(ValidateEcmpAndTunnel(flow, false, NULL, NULL)); + EXPECT_TRUE(ValidateEcmpAndTunnel(rflow, false, NULL, NULL)); + FlushFlowTable(); +} + +///////////////////////////////////////////////////////////////////////////// +// Egress Flow +// Source : Non-ECMP +// Destination : ECMP +///////////////////////////////////////////////////////////////////////////// +TEST_F(EcmpTest, Egress_Non_Ecmp_To_Ecmp_1) { + // Inner iteration for all 4 members of destination + for (int i = 0; i < 4; i++) { + char sip[64]; + strcpy(sip, REMOTE_NON_ECMP_IP_1); + char dip[64]; + VmInterface *vmi = static_cast(VmPortGet(i + 1)); + strcpy(dip, vmi->primary_ip_addr().to_string().c_str()); + + FlowEntry *flow = NULL; + FlowEntry *rflow = NULL; + // Generate layer-2 packet from fabric + EXPECT_TRUE(TxL2MplsPacketFromFabric(vmi, remote_server_ip_[0], + vmi->l2_label(), remote_mac_[0], + vmi->vm_mac().ToString().c_str(), + sip, dip, &flow, &rflow)); + + // Generate layer-3 ECMP resolve packet from VMI + EXPECT_TRUE(EcmpResolveFromVmi(vmi, dip, sip, rflow, flow)); + // Old forward flow must have ECMP index and point to same interface + EXPECT_TRUE(ValidateEcmpAndVmi(flow, true, dip, vmi)); + // Old reverse flow must not have ECMP + EXPECT_TRUE(ValidateEcmpAndTunnel(rflow, false, NULL, NULL)); + FlushFlowTable(); + } +} + +///////////////////////////////////////////////////////////////////////////// +// Egress Flow +// Source : ECMP +// Destination : Non-ECMP +///////////////////////////////////////////////////////////////////////////// +TEST_F(EcmpTest, Egress_Ecmp_To_Non_Ecmp_1) { + VmInterface *vmi = static_cast(VmPortGet(9)); + + for (int i = 0; i < 4; i++) { + char sip[64]; + strcpy(sip, REMOTE_ECMP_IP_1); + char dip[64]; + strcpy(dip, vmi->primary_ip_addr().to_string().c_str()); + + FlowEntry *flow = NULL; + FlowEntry *rflow = NULL; + // Generate layer-2 packet from fabric + EXPECT_TRUE(TxL2MplsPacketFromFabric(vmi, remote_server_ip_[i], + vmi->l2_label(), remote_mac_[i], + vmi->vm_mac().ToString().c_str(), + sip, dip, &flow, &rflow)); + + // Generate layer-3 ECMP resolve packet from VMI + EXPECT_TRUE(EcmpResolveFromVmi(vmi, dip, sip, rflow, flow)); + // Old forward flow must not have ECMP Index still + EXPECT_TRUE(ValidateEcmpAndTunnel(flow, false, NULL, NULL)); + // Old reverse flow must have ECMP Index set + EXPECT_TRUE(ValidateEcmpAndTunnel(rflow, true, sip, + remote_server_ip_[i])); + + FlushFlowTable(); + } +} + +///////////////////////////////////////////////////////////////////////////// +// Egress Flow +// Source : ECMP +// Destination : ECMP +///////////////////////////////////////////////////////////////////////////// +TEST_F(EcmpTest, Egress_Ecmp_To_Ecmp_1) { + // Outer iteration for all 4 members of source + for (int i = 0; i < 4; i++) { + // Inner iteration for all 4 members of destination + for (int j = 0; j < 4; j++) { + char sip[64]; + strcpy(sip, REMOTE_ECMP_IP_1); + char dip[64]; + VmInterface *vmi = static_cast(VmPortGet(j + 1)); + strcpy(dip, vmi->primary_ip_addr().to_string().c_str()); + + FlowEntry *flow = NULL; + FlowEntry *rflow = NULL; + string mac = vmi->vm_mac().ToString(); + EXPECT_TRUE(TxL2MplsPacketFromFabric(vmi, remote_server_ip_[i], + vmi->l2_label(), + remote_mac_[i], mac.c_str(), + sip, dip, &flow, &rflow)); + + // Generate layer-3 ECMP resolve packet from VMI + EXPECT_TRUE(EcmpResolveFromVmi(vmi, dip, sip, rflow, flow)); + // Old forward flow must have ECMP Index and point to VMI + EXPECT_TRUE(ValidateEcmpAndVmi(flow, true, dip, vmi)); + // Old reverse flow must have ECMP Index set and point to + // tunnel-source + EXPECT_TRUE(ValidateEcmpAndTunnel(rflow, true, sip, + remote_server_ip_[i])); + FlushFlowTable(); + } + } +} + +///////////////////////////////////////////////////////////////////////////// +// Ingress Flow +// Source : Non-ECMP +// Destination : Non-ECMP +///////////////////////////////////////////////////////////////////////////// +TEST_F(EcmpTest, Ingress_Non_Ecmp_To_Non_Ecmp_1) { + char sip[64]; + VmInterface *vmi = static_cast(VmPortGet(10)); + strcpy(sip, vmi->primary_ip_addr().to_string().c_str()); + string mac = vmi->vm_mac().ToString(); + char dip[64]; + strcpy(dip, REMOTE_NON_ECMP_IP_1); + + FlowEntry *flow = NULL; + FlowEntry *rflow = NULL; + // Send layer-2 packet from VMI + EXPECT_TRUE(TxL2PacketFromVmi(vmi, mac.c_str(), remote_mac_[0], + sip, dip, &flow, &rflow)); + + // Simulate ECMP Resolve from fabric + EXPECT_TRUE(EcmpResolveFromFabric(remote_server_ip_[0], vmi->label(), + dip, sip, rflow, flow)); + // Old forward flow must not have ECMP Index set + EXPECT_TRUE(ValidateEcmpAndTunnel(flow, false, NULL, NULL)); + // Old reverse flow must not have ECMP Index set + EXPECT_TRUE(ValidateEcmpAndTunnel(rflow, false, NULL, NULL)); + + FlushFlowTable(); +} + +///////////////////////////////////////////////////////////////////////////// +// Ingress Flow +// Source : Non-ECMP +// Destination : ECMP +///////////////////////////////////////////////////////////////////////////// +TEST_F(EcmpTest, Ingress_Non_Ecmp_To_Ecmp_1) { + VmInterface *vmi = static_cast(VmPortGet(9)); + for (uint32_t i = 0; i < 4; i++) { + char sip[64]; + strcpy(sip, vmi->primary_ip_addr().to_string().c_str()); + string mac = vmi->vm_mac().ToString(); + char dip[64]; + strcpy(dip, REMOTE_ECMP_IP_1); + + FlowEntry *flow = NULL; + FlowEntry *rflow = NULL; + // Send layer-2 packet from VMI + EXPECT_TRUE(TxL2PacketFromVmi(vmi, mac.c_str(), remote_mac_[0], + sip, dip, &flow, &rflow)); + + // Simulate ECMP Resolve from fabric + EXPECT_TRUE(EcmpResolveFromFabric(remote_server_ip_[i], vmi->label(), + dip, sip, rflow, flow)); + // Old forward flow must have ECMP Index set and point to tunnel source + EXPECT_TRUE(ValidateEcmpAndTunnel(flow, true, dip, + remote_server_ip_[i])); + // Old reverse flow must not have ECMP Index set + EXPECT_TRUE(ValidateEcmpAndTunnel(rflow, false, NULL, NULL)); + + FlushFlowTable(); + } +} + +///////////////////////////////////////////////////////////////////////////// +// Ingress Flow +// Source : ECMP +// Destination : Non-ECMP +///////////////////////////////////////////////////////////////////////////// +TEST_F(EcmpTest, Ingress_Ecmp_To_Non_Ecmp_1) { + for (uint32_t i = 0; i < 4; i++) { + char sip[64]; + VmInterface *vmi = static_cast(VmPortGet(i + 1)); + strcpy(sip, vmi->primary_ip_addr().to_string().c_str()); + string mac = vmi->vm_mac().ToString(); + char dip[64]; + strcpy(dip, REMOTE_NON_ECMP_IP_1); + + FlowEntry *flow = NULL; + FlowEntry *rflow = NULL; + // Send layer-2 packet from VMI + EXPECT_TRUE(TxL2PacketFromVmi(vmi, mac.c_str(), remote_mac_[0], + sip, dip, &flow, &rflow)); + + // Simulate ECMP Resolve from fabric + EXPECT_TRUE(EcmpResolveFromFabric(remote_server_ip_[i], vmi->label(), + dip, sip, rflow, flow)); + // Old forward flow must not have ECMP Index set + EXPECT_TRUE(ValidateEcmpAndTunnel(flow, false, NULL, NULL)); + // Old reverse flow must have ECMP Index set + EXPECT_TRUE(ValidateEcmpAndVmi(rflow, true, sip, vmi)); + + FlushFlowTable(); + } +} + +///////////////////////////////////////////////////////////////////////////// +// Ingress Flow +// Source : ECMP +// Destination : ECMP +///////////////////////////////////////////////////////////////////////////// +TEST_F(EcmpTest, Ingress_Ecmp_To_Ecmp_1) { + for (uint32_t i = 0; i < 4; i++) { + for (uint32_t j = 0; j < 4; j++) { + char sip[64]; + VmInterface *vmi = static_cast(VmPortGet(i + 1)); + strcpy(sip, vmi->primary_ip_addr().to_string().c_str()); + string mac = vmi->vm_mac().ToString(); + char dip[64]; + strcpy(dip, REMOTE_ECMP_IP_1); + + FlowEntry *flow = NULL; + FlowEntry *rflow = NULL; + // Send layer-2 packet from VMI + EXPECT_TRUE(TxL2PacketFromVmi(vmi, mac.c_str(), remote_mac_[i], + sip, dip, &flow, &rflow)); + + // Simulate ECMP Resolve from fabric + EXPECT_TRUE(EcmpResolveFromFabric(remote_server_ip_[i], + vmi->label(), dip, sip, rflow, + flow)); + // Old forward flow must not have ECMP Index set + EXPECT_TRUE(ValidateEcmpAndTunnel(flow, true, dip, + remote_server_ip_[i])); + // Old reverse flow must have ECMP Index set + EXPECT_TRUE(ValidateEcmpAndVmi(rflow, true, sip, vmi)); + + FlushFlowTable(); + } + } +} + +///////////////////////////////////////////////////////////////////////////// +// Local Flow +// Validate flows at source +// Source : Non-ECMP +// Destination : Non-ECMP +///////////////////////////////////////////////////////////////////////////// +TEST_F(EcmpTest, Local_Non_Ecmp_To_Non_Ecmp_1) { + VmInterface *vmi1 = static_cast(VmPortGet(9)); + VmInterface *vmi2 = static_cast(VmPortGet(10)); + + char sip[64]; + strcpy(sip, vmi1->primary_ip_addr().to_string().c_str()); + string mac1 = vmi1->vm_mac().ToString(); + char dip[64]; + strcpy(dip, vmi2->primary_ip_addr().to_string().c_str()); + string mac2 = vmi2->vm_mac().ToString(); + + FlowEntry *flow = NULL; + FlowEntry *rflow = NULL; + // Send layer-2 packet from VMI + EXPECT_TRUE(TxL2PacketFromVmi(vmi1, mac1.c_str(), mac2.c_str(), sip, + dip, &flow, &rflow)); + + // Simulate ECMP Resolve from vmi1 + EXPECT_TRUE(EcmpResolveFromVmi(vmi1, sip, dip, flow, rflow)); + // Old forward flow must not have ECMP Index set + EXPECT_TRUE(ValidateEcmpAndTunnel(flow, false, NULL, NULL)); + // Old reverse flow must not have ECMP Index set + EXPECT_TRUE(ValidateEcmpAndTunnel(rflow, false, NULL, NULL)); + + FlushFlowTable(); +} + +///////////////////////////////////////////////////////////////////////////// +// Local Flow +// Validate flows at source +// Source : Non-ECMP +// Destination : ECMP +///////////////////////////////////////////////////////////////////////////// +TEST_F(EcmpTest, Local_Non_Ecmp_To_Ecmp_1) { + for (uint32_t i = 0; i < 4; i++) { + VmInterface *vmi1 = static_cast(VmPortGet(9)); + VmInterface *vmi2 = static_cast(VmPortGet(i+1)); + char sip[64]; + strcpy(sip, vmi1->primary_ip_addr().to_string().c_str()); + string mac1 = vmi1->vm_mac().ToString(); + char dip[64]; + strcpy(dip, vmi2->primary_ip_addr().to_string().c_str()); + string mac2 = vmi2->vm_mac().ToString(); + + FlowEntry *flow = NULL; + FlowEntry *rflow = NULL; + // Send layer-2 packet from VMI + EXPECT_TRUE(TxL2PacketFromVmi(vmi1, mac1.c_str(), mac2.c_str(), + sip, dip, &flow, &rflow)); + + // Simulate ECMP Resolve from vmi2 + EXPECT_TRUE(EcmpResolveFromVmi(vmi2, dip, sip, flow, rflow)); + // Old forward flow will have ECMP Index set and must point to original + // destination + EXPECT_TRUE(ValidateEcmpAndVmi(flow, true, dip, vmi2)); + // Old reverse flow must not have ECMP Index set + EXPECT_TRUE(ValidateEcmpAndVmi(rflow, false, NULL, NULL)); + + FlushFlowTable(); + } +} + +///////////////////////////////////////////////////////////////////////////// +// Local Flow +// Validate flows at source +// Source : ECMP +// Destination : Non-ECMP +///////////////////////////////////////////////////////////////////////////// +TEST_F(EcmpTest, Local_Ecmp_To_Non_Ecmp_1) { + for (uint32_t i = 0; i < 4; i++) { + char sip[64]; + VmInterface *vmi1 = static_cast(VmPortGet(i + 1)); + strcpy(sip, vmi1->primary_ip_addr().to_string().c_str()); + string mac1 = vmi1->vm_mac().ToString(); + char dip[64]; + VmInterface *vmi2 = static_cast(VmPortGet(9)); + strcpy(dip, vmi2->primary_ip_addr().to_string().c_str()); + string mac2 = vmi2->vm_mac().ToString(); + + FlowEntry *flow = NULL; + FlowEntry *rflow = NULL; + // Send layer-2 packet from VMI + EXPECT_TRUE(TxL2PacketFromVmi(vmi1, mac1.c_str(), mac2.c_str(), + sip, dip, &flow, &rflow)); + + // Simulate ECMP Resolve from vmi2 + EXPECT_TRUE(EcmpResolveFromVmi(vmi2, dip, sip, rflow, flow)); + // Old forward flow must not have ECMP Index set + EXPECT_TRUE(ValidateEcmpAndTunnel(flow, false, NULL, NULL)); + // Old reverse flow must have ECMP Index set + EXPECT_TRUE(ValidateEcmpAndVmi(rflow, true, sip, vmi1)); + + FlushFlowTable(); + } +} + +///////////////////////////////////////////////////////////////////////////// +// Local Flow +// Validate flows at source +// Source : ECMP +// Destination : ECMP +///////////////////////////////////////////////////////////////////////////// +TEST_F(EcmpTest, Local_Ecmp_To_Ecmp_1) { + for (uint32_t i = 0; i < 4; i++) { + for (uint32_t j = 0; j < 4; j++) { + char sip[64]; + VmInterface *vmi1 = static_cast(VmPortGet(i + 1)); + strcpy(sip, vmi1->primary_ip_addr().to_string().c_str()); + string mac1 = vmi1->vm_mac().ToString(); + + char dip[64]; + VmInterface *vmi2 = static_cast(VmPortGet(j + 5)); + strcpy(dip, vmi2->primary_ip_addr().to_string().c_str()); + string mac2 = vmi2->vm_mac().ToString(); + + FlowEntry *flow = NULL; + FlowEntry *rflow = NULL; + // Send layer-2 packet from VMI + EXPECT_TRUE(TxL2PacketFromVmi(vmi1, mac1.c_str(), mac2.c_str(), + sip, dip, &flow, &rflow)); + + // Simulate ECMP Resolve from vmi + EXPECT_TRUE(EcmpResolveFromVmi(vmi2, dip, sip, rflow, flow)); + EXPECT_TRUE(ValidateEcmpAndVmi(flow, true, dip, vmi2)); + EXPECT_TRUE(ValidateEcmpAndVmi(rflow, true, sip, vmi1)); + + FlushFlowTable(); + } + } +} + +int main(int argc, char *argv[]) { + GETUSERARGS(); + client = TestInit(init_file, ksync_init, true, true, true, 100*1000); + int ret = RUN_ALL_TESTS(); + client->WaitForIdle(); + TestShutdown(); + delete client; + return ret; +} diff --git a/src/vnsw/agent/pkt/test/test_flow_trace_filter.cc b/src/vnsw/agent/pkt/test/test_flow_trace_filter.cc index 994df0ca4c2..09abb8a3573 100644 --- a/src/vnsw/agent/pkt/test/test_flow_trace_filter.cc +++ b/src/vnsw/agent/pkt/test/test_flow_trace_filter.cc @@ -17,21 +17,6 @@ struct PortInfo input[] = { {"vnet3", 3, "1.1.1.13", "00:00:01:01:01:03", 1, 1}, }; -static bool FlowStatsTimerStartStopTrigger (Agent *agent, bool stop) { - agent->flow_stats_manager()-> - default_flow_stats_collector()->TestStartStopTimer(stop); - return true; -} - -static void FlowStatsTimerStartStop (Agent *agent, bool stop) { - int task_id = agent->task_scheduler()->GetTaskId(kTaskFlowStatsCollector); - std::auto_ptr trigger_ - (new TaskTrigger(boost::bind(FlowStatsTimerStartStopTrigger, agent, - stop), task_id, 0)); - trigger_->Set(); - client->WaitForIdle(); -} - class FlowTraceFilterTest : public ::testing::Test { public: virtual void SetUp() { diff --git a/src/vnsw/agent/pkt/test/test_flowtable.cc b/src/vnsw/agent/pkt/test/test_flowtable.cc index d93818eed8b..fbf4860f6ca 100644 --- a/src/vnsw/agent/pkt/test/test_flowtable.cc +++ b/src/vnsw/agent/pkt/test/test_flowtable.cc @@ -87,7 +87,8 @@ FlowEntry *FlowInit(TestFlowKey *t, FlowTable *flow_table) { ctrl.intf_ = VmPortGet(t->ifindex_); ctrl.vm_ = VmGet(t->vm_); - flow->InitFwdFlow(&info, pkt, &ctrl, &ctrl, NULL, Agent::GetInstance()); + flow->InitFwdFlow(&info, pkt, &ctrl, &ctrl, NULL, Agent::GetInstance(), + info.l3_flow); return flow; } @@ -289,7 +290,8 @@ class FlowTableTest : public ::testing::Test { ctrl.intf_ = VmPortGet(t->ifindex_); ctrl.vm_ = VmGet(t->vm_); - flow->InitFwdFlow(&info, pkt, &ctrl, &ctrl, NULL, Agent::GetInstance()); + flow->InitFwdFlow(&info, pkt, &ctrl, &ctrl, NULL, Agent::GetInstance(), + info.l3_flow); return flow; } diff --git a/src/vnsw/agent/pkt/test/test_pkt_flow_limits.cc b/src/vnsw/agent/pkt/test/test_pkt_flow_limits.cc index 52d7c8cf744..3bca26007ed 100644 --- a/src/vnsw/agent/pkt/test/test_pkt_flow_limits.cc +++ b/src/vnsw/agent/pkt/test/test_pkt_flow_limits.cc @@ -60,22 +60,6 @@ VmInterface *vmi_2; VmInterface *vmi_3; std::string eth_itf; -static bool FlowStatsTimerStartStopTrigger (Agent *agent, bool stop) { - FlowStatsCollector *stats = - agent->flow_stats_manager()->default_flow_stats_collector(); - stats->TestStartStopTimer(stop); - return true; -} - -static void FlowStatsTimerStartStop (Agent *agent, bool stop) { - int task_id = agent->task_scheduler()->GetTaskId(kTaskFlowStatsCollector); - std::auto_ptr trigger_ - (new TaskTrigger(boost::bind(FlowStatsTimerStartStopTrigger, agent, - stop), task_id, 0)); - trigger_->Set(); - client->WaitForIdle(); -} - class FlowTest : public ::testing::Test { public: FlowTest() : agent_(Agent::GetInstance()) { diff --git a/src/vnsw/agent/pkt/test/test_pkt_util.cc b/src/vnsw/agent/pkt/test/test_pkt_util.cc index cf21b439b70..b3b14419dc5 100644 --- a/src/vnsw/agent/pkt/test/test_pkt_util.cc +++ b/src/vnsw/agent/pkt/test/test_pkt_util.cc @@ -426,3 +426,19 @@ void TxL2Ip6Packet(int ifindex, const char *smac, const char *dmac, pkt->GetBuffLen()); delete pkt; } + +static bool FlowStatsTimerStartStopTrigger (Agent *agent, bool stop) { + FlowStatsCollector *stats = + agent->flow_stats_manager()->default_flow_stats_collector(); + stats->TestStartStopTimer(stop); + return true; +} + +void FlowStatsTimerStartStop(Agent *agent, bool stop) { + int task_id = agent->task_scheduler()->GetTaskId(kTaskFlowStatsCollector); + std::auto_ptr trigger_ + (new TaskTrigger(boost::bind(FlowStatsTimerStartStopTrigger, agent, + stop), task_id, 0)); + trigger_->Set(); + client->WaitForIdle(); +} diff --git a/src/vnsw/agent/pkt/test/test_pkt_util.h b/src/vnsw/agent/pkt/test/test_pkt_util.h index c67d60471b3..ea56611cbe3 100644 --- a/src/vnsw/agent/pkt/test/test_pkt_util.h +++ b/src/vnsw/agent/pkt/test/test_pkt_util.h @@ -127,4 +127,6 @@ extern void TxL2Ip6Packet(int ifindex, const char *smac, const char *dmac, const char *sip, const char *dip, int proto, int hash_id = 1, int vrf = -1, uint16_t sport = 0, uint16_t dport = 0); + +void FlowStatsTimerStartStop(Agent *agent, bool stop); #endif // __TEST_PKT_UTIL_H__ diff --git a/src/vnsw/agent/test/pkt_gen.h b/src/vnsw/agent/test/pkt_gen.h index 1c5f25cb27a..759b06cb339 100644 --- a/src/vnsw/agent/test/pkt_gen.h +++ b/src/vnsw/agent/test/pkt_gen.h @@ -410,7 +410,6 @@ class PktGen { Agent::GetInstance()->mpls_table()->FindMplsLabel(label); if (mpls_label) { nh = mpls_label->nexthop()->id(); -#if 0 const InterfaceNH *intf_nh = dynamic_cast (mpls_label->nexthop()); if (intf_nh) { @@ -422,7 +421,6 @@ class PktGen { } } } -#endif } } else if (vxlan_id > 0) { VxLanId *vxlan = diff --git a/src/vnsw/agent/uve/test/test_port_bitmap.cc b/src/vnsw/agent/uve/test/test_port_bitmap.cc index 67710c20249..8f41deacda0 100644 --- a/src/vnsw/agent/uve/test/test_port_bitmap.cc +++ b/src/vnsw/agent/uve/test/test_port_bitmap.cc @@ -270,7 +270,8 @@ class UvePortBitmapTest : public ::testing::Test { ctrl.vn_ = vn; ctrl.intf_ = intf; - flow->InitFwdFlow(&info, pkt, &ctrl, &ctrl, NULL, Agent::GetInstance()); + flow->InitFwdFlow(&info, pkt, &ctrl, &ctrl, NULL, Agent::GetInstance(), + info.l3_flow); } void NewFlow(FlowEntry *f) { Agent *agent = Agent::GetInstance();