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();