Line data Source code
1 : /*
2 : * Copyright (c) 2017 Juniper Networks, Inc. All rights reserved.
3 : * Copyright Contributors to the OpenSDN project
4 : */
5 :
6 : #include <boost/foreach.hpp>
7 : #include <boost/uuid/uuid_io.hpp>
8 :
9 : #include <base/address_util.h>
10 : #include <base/task_annotations.h>
11 : #include <cmn/agent_cmn.h>
12 : #include <route/route.h>
13 : #include <oper/ecmp.h>
14 : #include <oper/ecmp_load_balance.h>
15 : #include <oper/route_common.h>
16 : #include <oper/vrf.h>
17 : #include <oper/tunnel_nh.h>
18 : #include <oper/mpls.h>
19 : #include <oper/vxlan.h>
20 : #include <oper/mirror_table.h>
21 : #include <oper/multicast.h>
22 : #include <oper/agent_sandesh.h>
23 :
24 : using namespace std;
25 : using namespace boost::asio;
26 :
27 3901 : EcmpData::EcmpData(Agent *agent,
28 : const string &vrf_name,
29 : const string &route_str,
30 : AgentPath *path,
31 3901 : bool del) :
32 3901 : path_(path), ecmp_path_(NULL), delete_(del),
33 3901 : alloc_label_(true), label_(MplsTable::kInvalidLabel),
34 3901 : vrf_name_(vrf_name), route_str_(route_str),
35 3901 : vn_list_(path->dest_vn_list()),
36 3901 : sg_list_(path->sg_list()),
37 3901 : tag_list_(path->tag_list()),
38 3901 : community_list_(path->communities()),
39 3901 : path_preference_(path->path_preference()),
40 3901 : tunnel_bmap_(path->tunnel_bmap()),
41 3901 : ecmp_load_balance_(path->ecmp_load_balance()),
42 7802 : nh_req_(), agent_(agent) {
43 3901 : }
44 :
45 3901 : bool EcmpData::Update(AgentRoute *rt) {
46 3901 : if (path_->peer() == NULL) {
47 0 : return false;
48 : }
49 :
50 3901 : if (path_->peer()->GetType() == Peer::LOCAL_VM_PORT_PEER) {
51 1332 : return LocalVmPortPeerEcmp(rt);
52 : }
53 :
54 2569 : if (path_->peer()->GetType() == Peer::BGP_PEER) {
55 155 : alloc_label_ = false;
56 155 : return BgpPeerEcmp();
57 : }
58 :
59 2414 : return false;
60 : }
61 :
62 11 : bool EcmpData::UpdateWithParams(const SecurityGroupList &sg_list,
63 : const TagList &tag_list,
64 : const CommunityList &community_list,
65 : const PathPreference &path_preference,
66 : const TunnelType::TypeBmap bmap,
67 : const EcmpLoadBalance &ecmp_load_balance,
68 : const VnListType &vn_list,
69 : DBRequest &nh_req) {
70 11 : sg_list_ = sg_list;
71 11 : vn_list_ = vn_list;
72 11 : tag_list_ = tag_list;
73 11 : community_list_ = community_list;
74 11 : path_preference_ = path_preference;
75 11 : tunnel_bmap_ = bmap;
76 11 : ecmp_load_balance_ = ecmp_load_balance;
77 11 : nh_req_.Swap(&nh_req);
78 :
79 11 : return Update(NULL);
80 : }
81 :
82 1332 : bool EcmpData::LocalVmPortPeerEcmp(AgentRoute *rt) {
83 1332 : ecmp_path_ = rt->FindPath(agent_->ecmp_peer());
84 1332 : if (delete_) {
85 281 : return EcmpDeletePath(rt);
86 : } else {
87 1051 : if (path_->path_preference().is_ecmp() == false) {
88 33 : return false;
89 : }
90 1018 : return EcmpAddPath(rt);
91 : }
92 : }
93 :
94 155 : bool EcmpData::BgpPeerEcmp() {
95 155 : ecmp_path_ = path_;
96 : //Bgp peer update for ecmp should always accompany nh_req.
97 : //Any other request like sync is to be ignored.
98 155 : NextHopKey *key = static_cast<NextHopKey *>(nh_req_.key.get());
99 155 : if (!key)
100 144 : return false;
101 11 : return ModifyEcmpPath();
102 : }
103 :
104 : // Handle add/update of a path in route.
105 : // If there are more than one path of type LOCAL_VM_PORT_PEER, creates/updates
106 : // Composite-NH for them
107 1018 : bool EcmpData::EcmpAddPath(AgentRoute *rt) {
108 1018 : if (path_->tunnel_bmap() & TunnelType::NativeType()) {
109 0 : path_->set_tunnel_bmap(TunnelType::MplsType() |
110 0 : TunnelType::NativeType());
111 : } else {
112 1018 : path_->set_tunnel_bmap(TunnelType::MplsType());
113 : }
114 :
115 : // Count number of paths from LOCAL_VM_PORT_PEER already present
116 1018 : const AgentPath *vm_port_path = NULL;
117 1018 : int count = 0;
118 5888 : for(Route::PathList::const_iterator it = rt->GetPathList().begin();
119 9740 : it != rt->GetPathList().end(); it++) {
120 : const AgentPath *it_path =
121 3852 : static_cast<const AgentPath *>(it.operator->());
122 :
123 3852 : if (it_path->peer() == agent_->ecmp_peer())
124 481 : assert(ecmp_path_ == it_path);
125 :
126 7704 : if (it_path->peer() &&
127 7704 : it_path->peer()->GetType() == Peer::LOCAL_VM_PORT_PEER &&
128 2039 : it_path->path_preference().is_ecmp() == true) {
129 2039 : count++;
130 2039 : if (it_path != path_)
131 1021 : vm_port_path = it_path;
132 : }
133 : }
134 :
135 1018 : if (count == 0) {
136 0 : return false;
137 : }
138 :
139 : // Sanity check. When more than one LOCAL_VM_PORT_PEER, ECMP must be present
140 1018 : if (count > 2) {
141 474 : assert(ecmp_path_ != NULL);
142 : }
143 :
144 1018 : if (count == 1) {
145 481 : assert(ecmp_path_ == NULL);
146 481 : return false;
147 : }
148 :
149 537 : bool ret = false;
150 537 : if (count == 2 && ecmp_path_ == NULL) {
151 : // This is second path being added, make ECMP
152 56 : AllocateEcmpPath(rt, vm_port_path);
153 56 : ret = true;
154 481 : } else if (count > 2) {
155 : // ECMP already present, add/update Component-NH for the path
156 474 : AppendEcmpPath(rt, path_);
157 474 : ret = true;
158 7 : } else if (ecmp_path_) {
159 7 : bool updated = UpdateComponentNH(rt, path_);
160 : //No update happened for component NH, so verify if params are to be
161 : //synced. If update of component NH is done, then params would also have
162 : //been updated, so no need to do it again.
163 7 : if (!updated) {
164 7 : updated = SyncParams();
165 : }
166 7 : if (updated) {
167 1 : ret = true;
168 : }
169 : }
170 :
171 537 : return ret;
172 : }
173 :
174 : // Function to create a ECMP path from path and path2
175 : // Creates Composite-NH with 2 Component-NH (one for each of path and path2)
176 : // Creates a new MPLS Label for the ECMP path
177 56 : void EcmpData::AllocateEcmpPath(AgentRoute *rt, const AgentPath *path2) {
178 : // Allocate and insert a path
179 56 : ecmp_path_ = new AgentPath(agent_->ecmp_peer(), rt);
180 56 : rt->InsertPath(ecmp_path_);
181 :
182 56 : const NextHop* path1_nh = path_->ComputeNextHop(agent_);
183 56 : bool composite_nh_policy = path1_nh->NexthopToInterfacePolicy();
184 :
185 : // Create Component NH to be added to ECMP path
186 56 : DBEntryBase::KeyPtr key1 = path1_nh->GetDBRequestKey();
187 56 : NextHopKey *nh_key1 = static_cast<NextHopKey *>(key1.release());
188 56 : std::unique_ptr<const NextHopKey> nh_akey1(nh_key1);
189 56 : nh_key1->SetPolicy(false);
190 56 : ComponentNHKeyPtr component_nh_data1(new ComponentNHKey(path_->label(),
191 56 : std::move(nh_akey1)));
192 :
193 56 : const NextHop* path2_nh = path2->ComputeNextHop(agent_);
194 56 : if (!composite_nh_policy) {
195 0 : composite_nh_policy = path2_nh->NexthopToInterfacePolicy();
196 : }
197 56 : DBEntryBase::KeyPtr key2 = path2_nh->GetDBRequestKey();
198 56 : NextHopKey *nh_key2 = static_cast<NextHopKey *>(key2.release());
199 56 : std::unique_ptr<const NextHopKey> nh_akey2(nh_key2);
200 56 : nh_key2->SetPolicy(false);
201 56 : ComponentNHKeyPtr component_nh_data2(new ComponentNHKey(path2->label(),
202 56 : std::move(nh_akey2)));
203 :
204 56 : ComponentNHKeyList component_nh_list;
205 56 : component_nh_list.push_back(component_nh_data2);
206 56 : component_nh_list.push_back(component_nh_data1);
207 :
208 : // Directly call AddChangePath to update NH in the ECMP path
209 : // It will also create CompositeNH if necessary
210 56 : DBRequest nh_req(DBRequest::DB_ENTRY_ADD_CHANGE);
211 56 : nh_req.key.reset(new CompositeNHKey(Composite::LOCAL_ECMP,
212 : composite_nh_policy, component_nh_list,
213 56 : vrf_name_));
214 56 : nh_req.data.reset(new CompositeNHData());
215 56 : nh_req_.Swap(&nh_req);
216 :
217 56 : label_ = MplsTable::kInvalidLabel;
218 56 : ModifyEcmpPath();
219 :
220 56 : RouteInfo rt_info;
221 56 : rt->FillTrace(rt_info, AgentRoute::CHANGE_PATH, ecmp_path_);
222 56 : AgentRouteTable *table = static_cast<AgentRouteTable *>(rt->get_table());
223 56 : OPER_TRACE_ROUTE_ENTRY(Route, table, rt_info);
224 56 : AGENT_ROUTE_LOG(table, "Path Add", rt->ToString(), vrf_name_,
225 : GETPEERNAME(agent_->ecmp_peer()));
226 56 : }
227 :
228 474 : void EcmpData::AppendEcmpPath(AgentRoute *rt, AgentPath *path) {
229 474 : assert(ecmp_path_);
230 474 : const NextHop* path_nh = path->ComputeNextHop(agent_);
231 474 : DBEntryBase::KeyPtr key = path_nh->GetDBRequestKey();
232 474 : NextHopKey *nh_key = static_cast<NextHopKey *>(key.release());
233 474 : std::unique_ptr<const NextHopKey> nh_akey(nh_key);
234 474 : nh_key->SetPolicy(false);
235 474 : ComponentNHKeyPtr comp_nh_key_ptr(new ComponentNHKey(path->label(), std::move(nh_akey)));
236 :
237 474 : ComponentNHKeyList component_nh_key_list;
238 : const CompositeNH *comp_nh =
239 474 : static_cast<const CompositeNH *>(ecmp_path_->ComputeNextHop(agent_));
240 474 : DBEntryBase::KeyPtr comp_nh_key = comp_nh->GetDBRequestKey();
241 474 : NextHopKey *cnh_key = static_cast<NextHopKey *>(comp_nh_key.get());
242 474 : bool composite_nh_policy = false;
243 948 : component_nh_key_list = comp_nh->AddComponentNHKey(comp_nh_key_ptr,
244 474 : composite_nh_policy);
245 : // Form the request for Inet4UnicastEcmpRoute and invoke AddChangePath
246 : // Get the existing comp_nh key and do a resync
247 474 : DBRequest nh_req(DBRequest::DB_ENTRY_ADD_CHANGE);
248 474 : cnh_key->sub_op_ = AgentKey::RESYNC;
249 : CompositeNHKey *composite_nh_key = new CompositeNHKey(Composite::LOCAL_ECMP,
250 : composite_nh_policy,
251 : component_nh_key_list,
252 474 : vrf_name_);
253 474 : nh_req.key = std::move(comp_nh_key);
254 474 : nh_req.data.reset(new CompositeNHData(component_nh_key_list));
255 474 : nh_req_.Swap(&nh_req);
256 :
257 474 : label_ = ecmp_path_->label();
258 474 : ModifyEcmpPath(composite_nh_key);
259 474 : path->SyncRoute(true);
260 :
261 474 : RouteInfo rt_info;
262 474 : rt->FillTrace(rt_info, AgentRoute::CHANGE_PATH, path);
263 474 : AgentRouteTable *table = static_cast<AgentRouteTable *>(rt->get_table());
264 474 : OPER_TRACE_ROUTE_ENTRY(Route, table, rt_info);
265 474 : AGENT_ROUTE_LOG(table, "Path change", rt->ToString(), vrf_name_,
266 : GETPEERNAME(agent_->ecmp_peer()));
267 474 : }
268 :
269 : // Handle deletion of a path in route. If the path being deleted is part of
270 : // ECMP, then deletes the Component-NH for the path.
271 : // Delete ECMP path if there is single Component-NH in Composite-NH
272 281 : bool EcmpData::EcmpDeletePath(AgentRoute *rt) {
273 281 : if (path_->peer() == NULL) {
274 0 : return false;
275 : }
276 :
277 281 : if (path_->peer()->GetType() != Peer::LOCAL_VM_PORT_PEER) {
278 0 : return false;
279 : }
280 :
281 : // Composite-NH is made from LOCAL_VM_PORT_PEER, count number of paths
282 : // with LOCAL_VM_PORT_PEER
283 281 : int count = 0;
284 1275 : for(Route::PathList::const_iterator it = rt->GetPathList().begin();
285 1988 : it != rt->GetPathList().end(); it++) {
286 : const AgentPath *it_path =
287 713 : static_cast<const AgentPath *>(it.operator->());
288 :
289 1426 : if (it_path->peer() &&
290 713 : it_path->peer()->GetType() == Peer::LOCAL_VM_PORT_PEER &&
291 1862 : it_path->path_preference().is_ecmp() == true &&
292 436 : it_path != path_)
293 170 : count++;
294 : }
295 :
296 : // Sanity check. When more than one LOCAL_VM_PORT_PEER, ECMP must be present
297 281 : if (count >= 1) {
298 112 : if (ecmp_path_ == NULL) {
299 0 : return false;
300 : }
301 : }
302 :
303 281 : if (count == 1 && ecmp_path_) {
304 : // There is single path of type LOCAL_VM_PORT_PEER. Delete the ECMP path
305 56 : rt->RemovePath(ecmp_path_);
306 : //Enqueue MPLS label delete request
307 56 : agent_->mpls_table()->FreeLabel(ecmp_path_->label());
308 56 : delete ecmp_path_;
309 225 : } else if (count > 1) {
310 : // Remove Component-NH for the path being deleted
311 56 : DeleteComponentNH(rt, path_);
312 : }
313 :
314 281 : return true;
315 : }
316 :
317 597 : bool EcmpData::UpdateNh(CompositeNHKey *composite_nh_key) {
318 597 : NextHop *nh = NULL;
319 597 : bool ret = false;
320 :
321 597 : agent_->nexthop_table()->Process(nh_req_);
322 597 : NextHopKey *key = static_cast<NextHopKey *>(nh_req_.key.get());
323 :
324 : // Create MPLS label and point it to Composite NH
325 597 : if (alloc_label_) {
326 1172 : label_ = agent_->mpls_table()->CreateRouteLabel(label_, key, vrf_name_,
327 586 : route_str_);
328 : }
329 :
330 597 : if (composite_nh_key) {
331 474 : key = static_cast<NextHopKey *>(composite_nh_key);
332 : }
333 :
334 1194 : nh = static_cast<NextHop *>(agent_->nexthop_table()->
335 597 : FindActiveEntry(key));
336 597 : if (nh == NULL) {
337 0 : VrfEntry *vrf = agent_->vrf_table()->FindVrfFromName(vrf_name_);
338 0 : if (vrf->IsDeleted())
339 0 : return ret;
340 0 : assert(0);
341 : }
342 :
343 597 : MplsLabel *mpls = agent_->mpls_table()->
344 597 : FindMplsLabel(label_);
345 597 : if (mpls && (ecmp_path_->local_ecmp_mpls_label() == NULL)) {
346 56 : ecmp_path_->set_local_ecmp_mpls_label(mpls);
347 : }
348 597 : if (ecmp_path_->ChangeNH(agent_, nh) == true)
349 179 : ret = true;
350 :
351 597 : return ret;
352 : }
353 :
354 548 : bool EcmpData::SyncParams() {
355 548 : bool ret = false;
356 :
357 548 : ecmp_path_->set_tunnel_bmap(tunnel_bmap_);
358 : TunnelType::Type new_tunnel_type =
359 548 : TunnelType::ComputeType(tunnel_bmap_);
360 548 : if (ecmp_path_->tunnel_type() != new_tunnel_type) {
361 0 : ecmp_path_->set_tunnel_type(new_tunnel_type);
362 0 : ret = true;
363 : }
364 :
365 548 : if (ecmp_path_->dest_vn_list() != vn_list_) {
366 65 : ecmp_path_->set_dest_vn_list(vn_list_);
367 65 : ret = true;
368 : }
369 :
370 548 : if (ecmp_path_->sg_list() != sg_list_) {
371 0 : ecmp_path_->set_sg_list(sg_list_);
372 0 : ret = true;
373 : }
374 :
375 548 : if (ecmp_path_->tag_list() != tag_list_) {
376 0 : ecmp_path_->set_tag_list(tag_list_);
377 0 : ret = true;
378 : }
379 :
380 548 : if (ecmp_path_->communities() != community_list_) {
381 0 : ecmp_path_->set_communities(community_list_);
382 0 : ret = true;
383 : }
384 :
385 548 : if (path_preference_ != ecmp_path_->path_preference()) {
386 125 : ecmp_path_->set_path_preference(path_preference_);
387 125 : ret = true;
388 : }
389 :
390 548 : if (ecmp_path_->ecmp_load_balance() != ecmp_load_balance_) {
391 0 : ecmp_path_->set_ecmp_load_balance(ecmp_load_balance_);
392 0 : ret = true;
393 : }
394 :
395 548 : return ret;
396 : }
397 :
398 541 : bool EcmpData::ModifyEcmpPath(CompositeNHKey *composite_nh_key) {
399 541 : bool ret = false;
400 :
401 541 : if (UpdateNh(composite_nh_key)) {
402 123 : ret = true;
403 : }
404 :
405 541 : if (ecmp_path_->label() != label_) {
406 57 : ecmp_path_->set_label(label_);
407 57 : ret = true;
408 : }
409 :
410 541 : if (SyncParams()) {
411 133 : ret = true;
412 : }
413 :
414 541 : ecmp_path_->set_unresolved(false);
415 541 : ret = true;
416 :
417 541 : return ret;
418 : }
419 :
420 : /* When label of VMI changes and if that VMI (ie VMI's InterfaceNH) is part of
421 : * ECMP, then update the CompositeNH for ECMP route to point to right label for
422 : * that VMI. Label of VMI can change when policy-status of VMI changes */
423 7 : bool EcmpData::UpdateComponentNH(AgentRoute *rt, AgentPath *path) {
424 7 : if (!ecmp_path_) {
425 0 : return false;
426 : }
427 : //Build ComponentNHKey for new path
428 7 : const NextHop* path_nh = path->ComputeNextHop(agent_);
429 7 : DBEntryBase::KeyPtr key = path_nh->GetDBRequestKey();
430 7 : NextHopKey *nh_key = static_cast<NextHopKey *>(key.get());
431 7 : nh_key->SetPolicy(false);
432 :
433 7 : ComponentNHKeyList component_nh_key_list;
434 : const CompositeNH *comp_nh =
435 7 : static_cast<const CompositeNH *>(ecmp_path_->ComputeNextHop(agent_));
436 7 : bool composite_nh_policy = false;
437 7 : bool updated = comp_nh->UpdateComponentNHKey(path->label(), nh_key,
438 : component_nh_key_list,
439 : composite_nh_policy);
440 :
441 7 : if (!updated) {
442 7 : return false;
443 : }
444 : // Form the request for Inet4UnicastEcmpRoute and invoke AddChangePath
445 0 : DBRequest nh_req(DBRequest::DB_ENTRY_ADD_CHANGE);
446 0 : nh_req.key.reset(new CompositeNHKey(Composite::LOCAL_ECMP,
447 : composite_nh_policy,
448 : component_nh_key_list,
449 0 : vrf_name_));
450 0 : nh_req.data.reset(new CompositeNHData());
451 0 : nh_req_.Swap(&nh_req);
452 0 : label_ = ecmp_path_->label();
453 0 : ModifyEcmpPath();
454 :
455 0 : RouteInfo rt_info;
456 0 : rt->FillTrace(rt_info, AgentRoute::CHANGE_PATH, path);
457 0 : AgentRouteTable *table = static_cast<AgentRouteTable *>(rt->get_table());
458 0 : OPER_TRACE_ROUTE_ENTRY(Route, table, rt_info);
459 0 : AGENT_ROUTE_LOG(table, "Path Update", rt->ToString(), vrf_name_,
460 : GETPEERNAME(agent_->ecmp_peer()));
461 0 : return true;
462 7 : }
463 :
464 56 : void EcmpData::DeleteComponentNH(AgentRoute *rt, AgentPath *path) {
465 56 : assert(ecmp_path_);
466 56 : DBEntryBase::KeyPtr key = path->ComputeNextHop(agent_)->GetDBRequestKey();
467 56 : NextHopKey *nh_key = static_cast<NextHopKey *>(key.release());
468 56 : std::unique_ptr<const NextHopKey> nh_akey(nh_key);
469 56 : nh_key->SetPolicy(false);
470 56 : ComponentNHKeyPtr comp_nh_key_ptr(new ComponentNHKey(path->label(), std::move(nh_akey)));
471 :
472 56 : ComponentNHKeyList component_nh_key_list;
473 56 : bool comp_nh_policy = false;
474 : const CompositeNH *comp_nh =
475 56 : static_cast<const CompositeNH *>(ecmp_path_->ComputeNextHop(agent_));
476 112 : component_nh_key_list = comp_nh->DeleteComponentNHKey(comp_nh_key_ptr,
477 56 : comp_nh_policy);
478 :
479 : // Form the request for Inet4UnicastEcmpRoute and invoke AddChangePath
480 56 : DBRequest nh_req(DBRequest::DB_ENTRY_ADD_CHANGE);
481 56 : nh_req.key.reset(new CompositeNHKey(Composite::LOCAL_ECMP,
482 : comp_nh_policy, component_nh_key_list,
483 56 : vrf_name_));
484 56 : nh_req.data.reset(new CompositeNHData());
485 56 : nh_req_.Swap(&nh_req);
486 56 : label_ = ecmp_path_->label();
487 56 : UpdateNh();
488 :
489 56 : RouteInfo rt_info;
490 56 : rt->FillTrace(rt_info, AgentRoute::CHANGE_PATH, path);
491 56 : AgentRouteTable *table = static_cast<AgentRouteTable *>(rt->get_table());
492 56 : OPER_TRACE_ROUTE_ENTRY(Route, table, rt_info);
493 56 : AGENT_ROUTE_LOG(table, "Path change", rt->ToString(), vrf_name_,
494 : GETPEERNAME(agent_->ecmp_peer()));
495 56 : }
496 :
497 309 : const NextHop* EcmpData::GetLocalNextHop(const AgentRoute *rt) {
498 : Agent *agent =
499 309 : (static_cast<InetUnicastAgentRouteTable *> (rt->get_table()))->agent();
500 :
501 309 : if (rt->FindPath(agent->ecmp_peer())) {
502 254 : return rt->FindPath(agent->ecmp_peer())->ComputeNextHop(agent);
503 : }
504 :
505 : //If a route is leaked, and it points to local composite nexthop
506 : //then choose that
507 55 : if (rt->GetActivePath()->local_ecmp_mpls_label()) {
508 0 : return rt->GetActivePath()->local_ecmp_mpls_label()->nexthop();
509 : }
510 :
511 : //Choose the first local vm peer path
512 82 : for (Route::PathList::const_iterator it = rt->GetPathList().begin();
513 164 : it != rt->GetPathList().end(); it++) {
514 62 : const AgentPath *path = static_cast<const AgentPath *>(it.operator->());
515 62 : if (path) {
516 124 : if (path->peer() &&
517 62 : path->peer()->GetType() == Peer::LOCAL_VM_PORT_PEER) {
518 35 : return path->ComputeNextHop(agent);
519 : }
520 : }
521 : }
522 :
523 20 : const NextHop *nh = rt->GetActiveNextHop();
524 20 : if (nh && nh->GetType() == NextHop::COMPOSITE ) {
525 20 : const CompositeNH *comp_nh = static_cast<const CompositeNH *>(nh);
526 : //Get the local composite NH
527 20 : return comp_nh->GetLocalNextHop();
528 : }
529 0 : return NULL;
530 : }
|