Line data Source code
1 : /*
2 : * Copyright (c) 2015 Juniper Networks, Inc. All rights reserved.
3 : */
4 :
5 : #include "bgp/routing-policy/routing_policy.h"
6 :
7 : #include <boost/assign/list_of.hpp>
8 : #include <boost/foreach.hpp>
9 :
10 : #include "base/task_annotations.h"
11 : #include "base/task_trigger.h"
12 : #include "bgp/bgp_config.h"
13 : #include "bgp/bgp_factory.h"
14 : #include "bgp/bgp_server.h"
15 : #include "bgp/bgp_table.h"
16 : #include "bgp/routing-instance/routing_instance.h"
17 : #include "bgp/routing-policy/routing_policy_action.h"
18 : #include "bgp/routing-policy/routing_policy_match.h"
19 :
20 :
21 : class RoutingPolicyMgr::DeleteActor : public LifetimeActor {
22 : public:
23 32 : explicit DeleteActor(RoutingPolicyMgr *manager)
24 32 : : LifetimeActor(manager->server_->lifetime_manager()),
25 32 : manager_(manager) {
26 32 : }
27 32 : virtual bool MayDelete() const {
28 32 : return true;
29 : }
30 32 : virtual void Shutdown() {
31 32 : }
32 32 : virtual void Destroy() {
33 : // memory is deallocated by BgpServer scoped_ptr.
34 32 : manager_->server_delete_ref_.Reset(NULL);
35 32 : }
36 :
37 : private:
38 : RoutingPolicyMgr *manager_;
39 : };
40 :
41 32 : RoutingPolicyMgr::RoutingPolicyMgr(BgpServer *server) :
42 32 : server_(server),
43 32 : deleter_(new DeleteActor(this)),
44 32 : server_delete_ref_(this, server->deleter()),
45 64 : trace_buf_(SandeshTraceBufferCreate("RoutingPolicyMgr", 500)) {
46 32 : }
47 :
48 64 : RoutingPolicyMgr::~RoutingPolicyMgr() {
49 64 : }
50 :
51 32 : void RoutingPolicyMgr::ManagedDelete() {
52 32 : deleter_->Delete();
53 32 : }
54 :
55 0 : LifetimeActor *RoutingPolicyMgr::deleter() {
56 0 : return deleter_.get();
57 : }
58 :
59 0 : bool RoutingPolicyMgr::deleted() {
60 0 : return deleter()->IsDeleted();
61 : }
62 :
63 0 : RoutingPolicy *RoutingPolicyMgr::CreateRoutingPolicy(
64 : const BgpRoutingPolicyConfig *config) {
65 0 : RoutingPolicy *policy = GetRoutingPolicy(config->name());
66 :
67 0 : if (policy) {
68 0 : if (policy->deleted()) {
69 0 : return NULL;
70 : }
71 0 : return policy;
72 : }
73 :
74 0 : policy = BgpStaticObjectFactory::Create<RoutingPolicy>(
75 0 : config->name(), server_, this, config);
76 0 : routing_policies_.insert(std::make_pair(config->name(), policy));
77 0 : policy->ProcessConfig();
78 :
79 0 : return policy;
80 : }
81 :
82 0 : void RoutingPolicyMgr::UpdateRoutingPolicy(
83 : const BgpRoutingPolicyConfig *config) {
84 0 : CHECK_CONCURRENCY("bgp::Config");
85 :
86 0 : RoutingPolicy *policy = GetRoutingPolicy(config->name());
87 0 : if (policy && policy->deleted()) {
88 0 : return;
89 0 : } else if (!policy) {
90 0 : return;
91 : }
92 :
93 0 : policy->UpdateConfig(config);
94 : }
95 :
96 : //
97 : // Concurrency: BGP Config task
98 : //
99 : // Trigger deletion of a particular routing-policy
100 : //
101 0 : void RoutingPolicyMgr::DeleteRoutingPolicy(const std::string &name) {
102 0 : CHECK_CONCURRENCY("bgp::Config");
103 :
104 0 : RoutingPolicy *policy = GetRoutingPolicy(name);
105 :
106 0 : if (policy && policy->deleted()) {
107 0 : return;
108 0 : } else if (!policy) {
109 0 : return;
110 : }
111 :
112 0 : policy->ClearConfig();
113 :
114 0 : policy->ManagedDelete();
115 : }
116 :
117 : //
118 : // Concurrency: Called from BGP config task manager
119 : //
120 0 : void RoutingPolicyMgr::DestroyRoutingPolicy(RoutingPolicy *policy) {
121 0 : CHECK_CONCURRENCY("bgp::Config");
122 :
123 0 : const std::string name = policy->name();
124 0 : routing_policies_.erase(name);
125 0 : delete policy;
126 :
127 0 : if (deleted()) return;
128 :
129 : const BgpRoutingPolicyConfig *config
130 0 : = server()->config_manager()->FindRoutingPolicy(name);
131 0 : if (config) {
132 0 : CreateRoutingPolicy(config);
133 0 : return;
134 : }
135 0 : }
136 :
137 : // Given a routing instance re-evaluate routes/paths by applying routing policy
138 : // Walks all the tables of the given routing instance and apply the policy
139 : // This function puts the table into the walk request queue and triggers the
140 : // task to start the actual walk
141 0 : void RoutingPolicyMgr::ApplyRoutingPolicy(RoutingInstance *instance) {
142 0 : CHECK_CONCURRENCY("bgp::Config", "bgp::ConfigHelper");
143 :
144 0 : std::scoped_lock lock(mutex_);
145 0 : BOOST_FOREACH(RoutingInstance::RouteTableList::value_type &entry,
146 : instance->GetTables()) {
147 0 : BgpTable *table = entry.second;
148 0 : if (table->IsRoutingPolicySupported())
149 0 : RequestWalk(table);
150 : }
151 0 : }
152 :
153 : // On a given path of the route, apply the policy
154 0 : RoutingPolicy::PolicyResult RoutingPolicyMgr::ExecuteRoutingPolicy(
155 : const RoutingPolicy *policy, const BgpRoute *route,
156 : const BgpPath *path, BgpAttr *attr) const {
157 0 : return (*policy)(route, path, attr);
158 : }
159 :
160 : //
161 : // Concurrency: Called in the context of the DB partition task.
162 : // On a given route, apply routing policy
163 : // Walk through all the paths of the given route, and evaluate the result of the
164 : // routing policy
165 : //
166 0 : bool RoutingPolicyMgr::EvaluateRoutingPolicy(DBTablePartBase *root,
167 : DBEntryBase *entry) {
168 0 : CHECK_CONCURRENCY("db::DBTable");
169 :
170 0 : BgpTable *table = static_cast<BgpTable *>(root->parent());
171 0 : BgpRoute *route = static_cast<BgpRoute *>(entry);
172 0 : const RoutingInstance *rtinstance = table->routing_instance();
173 0 : if (route->IsDeleted()) return true;
174 :
175 0 : bool sort_and_notify = false;
176 0 : const Path *prev_front = route->front();
177 0 : for (Route::PathList::iterator it = route->GetPathList().begin();
178 0 : it != route->GetPathList().end(); ++it) {
179 0 : BgpPath *path = static_cast<BgpPath *>(it.operator->());
180 0 : uint32_t old_flags = path->GetFlags();
181 0 : const BgpAttr *old_attr = path->GetAttr();
182 0 : rtinstance->ProcessRoutingPolicy(route, path);
183 0 : if ((sort_and_notify == false) &&
184 0 : (old_flags != path->GetFlags() || old_attr != path->GetAttr())) {
185 0 : sort_and_notify = true;
186 : }
187 : }
188 :
189 0 : if (sort_and_notify) {
190 0 : route->Sort(&BgpTable::PathSelection, prev_front);
191 0 : root->Notify(entry);
192 : }
193 0 : return true;
194 : }
195 :
196 : //
197 : //
198 0 : bool RoutingPolicyMgr::UpdateRoutingPolicyList(
199 : const RoutingPolicyConfigList &cfg_list,
200 : RoutingPolicyAttachList *oper_list) {
201 0 : CHECK_CONCURRENCY("bgp::Config", "bgp::ConfigHelper");
202 :
203 0 : std::scoped_lock lock(mutex_);
204 0 : bool update_policy = false;
205 : // Number of routing policies is different
206 0 : if (oper_list->size() != cfg_list.size())
207 0 : update_policy = true;
208 :
209 0 : RoutingPolicyAttachList::iterator oper_it = oper_list->begin(), oper_next;
210 0 : RoutingPolicyConfigList::const_iterator config_it = cfg_list.begin();
211 0 : while (oper_it != oper_list->end() &&
212 0 : config_it != cfg_list.end()) {
213 : // Compare the configured routing policies on the routing-instance
214 : // with operational data.
215 0 : if (oper_it->first->name() == config_it->routing_policy_) {
216 0 : if (oper_it->second != oper_it->first->generation()) {
217 : // Policy content is updated
218 0 : oper_it->second = oper_it->first->generation();
219 0 : update_policy = true;
220 : }
221 0 : ++oper_it;
222 0 : ++config_it;
223 : } else {
224 : // Policy Order is updated or new policy is added
225 : // or policy is deleted
226 : RoutingPolicy *policy =
227 0 : GetRoutingPolicy(config_it->routing_policy_);
228 0 : if (policy) {
229 0 : *oper_it = std::make_pair(policy, policy->generation());
230 0 : ++oper_it;
231 0 : ++config_it;
232 0 : update_policy = true;
233 : } else {
234 : // points to routing policy that doesn't exists
235 : // will revisit in next config notification
236 0 : ++config_it;
237 : }
238 : }
239 : }
240 0 : for (oper_next = oper_it; oper_it != oper_list->end();
241 0 : oper_it = oper_next) {
242 : // Existing policy(ies) are removed
243 0 : ++oper_next;
244 0 : oper_list->erase(oper_it);
245 0 : update_policy = true;
246 : }
247 0 : for (; config_it != cfg_list.end(); ++config_it) {
248 : // new policy(ies) are added
249 0 : RoutingPolicy *policy = GetRoutingPolicy(config_it->routing_policy_);
250 0 : if (policy) {
251 0 : oper_list->push_back(std::make_pair(policy, policy->generation()));
252 : }
253 0 : update_policy = true;
254 : }
255 :
256 0 : return update_policy;
257 0 : }
258 :
259 : void
260 0 : RoutingPolicyMgr::RequestWalk(BgpTable *table) {
261 0 : CHECK_CONCURRENCY("bgp::Config", "bgp::ConfigHelper");
262 0 : RoutingPolicyWalkRequests::iterator it = routing_policy_sync_.find(table);
263 0 : if (it == routing_policy_sync_.end()) {
264 : DBTable::DBTableWalkRef walk_ref = table->AllocWalker(
265 : boost::bind(&RoutingPolicyMgr::EvaluateRoutingPolicy, this, _1, _2),
266 0 : boost::bind(&RoutingPolicyMgr::WalkDone, this, _2));
267 0 : table->WalkTable(walk_ref);
268 0 : routing_policy_sync_.insert(std::make_pair(table, walk_ref));
269 0 : } else {
270 0 : table->WalkAgain(it->second);
271 : }
272 0 : }
273 :
274 : void
275 0 : RoutingPolicyMgr::WalkDone(DBTableBase *dbtable) {
276 0 : CHECK_CONCURRENCY("db::Walker");
277 0 : BgpTable *table = static_cast<BgpTable *>(dbtable);
278 0 : RoutingPolicyWalkRequests::iterator it = routing_policy_sync_.find(table);
279 0 : assert(it != routing_policy_sync_.end());
280 0 : DBTable::DBTableWalkRef walk_ref = it->second;
281 0 : routing_policy_sync_.erase(it);
282 0 : table->ReleaseWalker(walk_ref);
283 0 : }
284 :
285 : class RoutingPolicy::DeleteActor : public LifetimeActor {
286 : public:
287 0 : DeleteActor(BgpServer *server, RoutingPolicy *parent)
288 0 : : LifetimeActor(server->lifetime_manager()), parent_(parent) {
289 0 : }
290 0 : virtual bool MayDelete() const {
291 0 : return parent_->MayDelete();
292 : }
293 0 : virtual void Shutdown() {
294 0 : parent_->Shutdown();
295 0 : }
296 0 : virtual void Destroy() {
297 0 : parent_->mgr_->DestroyRoutingPolicy(parent_);
298 0 : }
299 :
300 : private:
301 : RoutingPolicy *parent_;
302 : };
303 :
304 0 : RoutingPolicy::RoutingPolicy(std::string name, BgpServer *server,
305 : RoutingPolicyMgr *mgr,
306 0 : const BgpRoutingPolicyConfig *config)
307 0 : : name_(name), server_(server), mgr_(mgr), config_(config),
308 0 : deleter_(new DeleteActor(server, this)),
309 0 : manager_delete_ref_(this, mgr->deleter()), generation_(0) {
310 0 : refcount_ = 0;
311 0 : }
312 :
313 0 : RoutingPolicy::~RoutingPolicy() {
314 0 : terms_.clear();
315 0 : }
316 :
317 0 : RoutingPolicy::PolicyTermPtr RoutingPolicy::BuildTerm(
318 : const RoutingPolicyTermConfig &cfg_term) {
319 0 : PolicyTerm::ActionList actions;
320 0 : PolicyTerm::MatchList matches;
321 :
322 0 : if (!cfg_term.match.community_match.empty()) {
323 : MatchCommunity *community = new MatchCommunity(
324 0 : cfg_term.match.community_match,
325 0 : cfg_term.match.community_match_all);
326 0 : matches.push_back(community);
327 : }
328 :
329 0 : if (!cfg_term.match.ext_community_match.empty()) {
330 : MatchExtCommunity *ext_community = new MatchExtCommunity(
331 0 : cfg_term.match.ext_community_match,
332 0 : cfg_term.match.ext_community_match_all);
333 0 : matches.push_back(ext_community);
334 : }
335 :
336 0 : if (!cfg_term.match.protocols_match.empty()) {
337 : MatchProtocol *protocol =
338 0 : new MatchProtocol(cfg_term.match.protocols_match);
339 0 : matches.push_back(protocol);
340 : }
341 :
342 0 : if (!cfg_term.match.prefixes_to_match.empty()) {
343 0 : PrefixMatchConfigList inet_prefix_list;
344 0 : PrefixMatchConfigList inet6_prefix_list;
345 0 : BOOST_FOREACH(PrefixMatchConfig match,
346 : cfg_term.match.prefixes_to_match) {
347 0 : boost::system::error_code ec;
348 0 : Ip4Address ip4;
349 : int plen;
350 0 : ec = Ip4PrefixParse(match.prefix_to_match, &ip4, &plen);
351 0 : if (ec.value() == 0) {
352 0 : inet_prefix_list.push_back(match);
353 : } else {
354 0 : Ip6Address ip6;
355 0 : ec = Inet6PrefixParse(match.prefix_to_match, &ip6, &plen);
356 0 : if (ec.value() == 0) {
357 0 : inet6_prefix_list.push_back(match);
358 : }
359 : }
360 0 : }
361 0 : if (!inet_prefix_list.empty()) {
362 0 : MatchPrefixInet *prefix = new MatchPrefixInet(inet_prefix_list);
363 0 : matches.push_back(prefix);
364 : }
365 0 : if (!inet6_prefix_list.empty()) {
366 0 : MatchPrefixInet6 *prefix = new MatchPrefixInet6(inet6_prefix_list);
367 0 : matches.push_back(prefix);
368 : }
369 0 : }
370 :
371 : // Build the Action object
372 0 : if (cfg_term.action.action == RoutingPolicyActionConfig::REJECT) {
373 0 : RoutingPolicyRejectAction *action = new RoutingPolicyRejectAction();
374 0 : actions.push_back(action);
375 0 : } else if (cfg_term.action.action == RoutingPolicyActionConfig::NEXT_TERM) {
376 0 : RoutingPolicyNexTermAction *action = new RoutingPolicyNexTermAction();
377 0 : actions.push_back(action);
378 0 : } else if (cfg_term.action.action == RoutingPolicyActionConfig::ACCEPT) {
379 0 : RoutingPolicyAcceptAction *action = new RoutingPolicyAcceptAction();
380 0 : actions.push_back(action);
381 : }
382 :
383 0 : if (!cfg_term.action.update.aspath_expand.empty()) {
384 : UpdateAsPath *expand_aspath =
385 0 : new UpdateAsPath(cfg_term.action.update.aspath_expand);
386 0 : actions.push_back(expand_aspath);
387 : }
388 :
389 0 : if (!cfg_term.action.update.community_set.empty()) {
390 : UpdateCommunity *set_comm =
391 0 : new UpdateCommunity(cfg_term.action.update.community_set, "set");
392 0 : actions.push_back(set_comm);
393 : }
394 :
395 0 : if (!cfg_term.action.update.community_remove.empty()) {
396 : UpdateCommunity *remove_comm =
397 0 : new UpdateCommunity(cfg_term.action.update.community_remove, "remove");
398 0 : actions.push_back(remove_comm);
399 : }
400 :
401 0 : if (!cfg_term.action.update.community_add.empty()) {
402 : UpdateCommunity *add_comm =
403 0 : new UpdateCommunity(cfg_term.action.update.community_add, "add");
404 0 : actions.push_back(add_comm);
405 : }
406 :
407 0 : if (!cfg_term.action.update.ext_community_set.empty()) {
408 : UpdateExtCommunity *set_comm = new UpdateExtCommunity(
409 0 : cfg_term.action.update.ext_community_set, "set");
410 0 : actions.push_back(set_comm);
411 : }
412 :
413 0 : if (!cfg_term.action.update.ext_community_remove.empty()) {
414 : UpdateExtCommunity *remove_comm = new UpdateExtCommunity(
415 0 : cfg_term.action.update.ext_community_remove, "remove");
416 0 : actions.push_back(remove_comm);
417 : }
418 :
419 0 : if (!cfg_term.action.update.ext_community_add.empty()) {
420 : UpdateExtCommunity *add_comm = new UpdateExtCommunity(
421 0 : cfg_term.action.update.ext_community_add, "add");
422 0 : actions.push_back(add_comm);
423 : }
424 :
425 0 : if (cfg_term.action.update.local_pref) {
426 : UpdateLocalPref *local_pref =
427 0 : new UpdateLocalPref(cfg_term.action.update.local_pref);
428 0 : actions.push_back(local_pref);
429 : }
430 :
431 0 : if (cfg_term.action.update.med) {
432 : UpdateMed *med =
433 0 : new UpdateMed(cfg_term.action.update.med);
434 0 : actions.push_back(med);
435 : }
436 :
437 0 : PolicyTermPtr ret_term;
438 0 : if (!actions.empty() || !matches.empty()) {
439 0 : ret_term = PolicyTermPtr(new PolicyTerm());
440 0 : ret_term->set_actions(actions);
441 0 : ret_term->set_matches(matches);
442 : }
443 :
444 0 : return ret_term;
445 0 : }
446 :
447 0 : void RoutingPolicy::ProcessConfig() {
448 0 : BOOST_FOREACH(const RoutingPolicyTermConfig cfg_term, config_->terms()) {
449 : // Build each terms and insert to operational data
450 0 : PolicyTermPtr term = BuildTerm(cfg_term);
451 0 : if (term)
452 0 : add_term(term);
453 0 : }
454 0 : }
455 :
456 : //
457 : // Reprogram policy terms based on new config.
458 : // If the policy term has changed (number of terms got updated, or new term is
459 : // added or earlier term is deleted or existing term is updated), increment the
460 : // generation number to indicate the update
461 : //
462 0 : void RoutingPolicy::UpdateConfig(const BgpRoutingPolicyConfig *cfg) {
463 0 : CHECK_CONCURRENCY("bgp::Config");
464 0 : config_ = cfg;
465 0 : bool update_policy = false;
466 0 : if (terms()->size() != config_->terms().size())
467 0 : update_policy = true;
468 :
469 0 : RoutingPolicyTermList::iterator oper_it = terms()->begin(), oper_next;
470 : BgpRoutingPolicyConfig::RoutingPolicyTermList::const_iterator
471 0 : config_it = config_->terms().begin();
472 0 : while (oper_it != terms()->end() && config_it != config_->terms().end()) {
473 0 : PolicyTermPtr term = BuildTerm(*config_it);
474 0 : if (**oper_it == *term) {
475 0 : ++oper_it;
476 0 : ++config_it;
477 : } else {
478 0 : if (term) {
479 0 : *oper_it = term;
480 0 : update_policy = true;
481 0 : ++oper_it;
482 0 : ++config_it;
483 : } else {
484 0 : ++config_it;
485 : }
486 : }
487 0 : }
488 0 : for (oper_next = oper_it; oper_it != terms()->end(); oper_it = oper_next) {
489 0 : ++oper_next;
490 0 : terms()->erase(oper_it);
491 0 : update_policy = true;
492 : }
493 0 : for (; config_it != config_->terms().end(); ++config_it) {
494 0 : PolicyTermPtr term = BuildTerm(*config_it);
495 0 : if (term)
496 0 : add_term(term);
497 0 : update_policy = true;
498 0 : }
499 :
500 0 : if (update_policy) generation_++;
501 0 : }
502 :
503 0 : void RoutingPolicy::ClearConfig() {
504 0 : CHECK_CONCURRENCY("bgp::Config");
505 0 : config_ = NULL;
506 0 : }
507 :
508 0 : void RoutingPolicy::ManagedDelete() {
509 0 : deleter_->Delete();
510 0 : }
511 :
512 0 : void RoutingPolicy::Shutdown() {
513 0 : CHECK_CONCURRENCY("bgp::Config");
514 0 : ClearConfig();
515 0 : }
516 :
517 0 : bool RoutingPolicy::MayDelete() const {
518 0 : return (refcount_ == 0);
519 : }
520 :
521 0 : LifetimeActor *RoutingPolicy::deleter() {
522 0 : return deleter_.get();
523 : }
524 :
525 0 : const LifetimeActor *RoutingPolicy::deleter() const {
526 0 : return deleter_.get();
527 : }
528 :
529 0 : bool RoutingPolicy::deleted() const {
530 0 : return deleter()->IsDeleted();
531 : }
532 :
533 : //
534 : // Attempt to enqueue a delete for the RoutingPolicy.
535 : //
536 0 : void RoutingPolicy::RetryDelete() {
537 0 : if (!deleter_->IsDeleted())
538 0 : return;
539 0 : deleter_->RetryDelete();
540 : }
541 :
542 0 : RoutingPolicy::PolicyResult RoutingPolicy::operator()(const BgpRoute *route,
543 : const BgpPath *path, BgpAttr *attr) const {
544 0 : BOOST_FOREACH(PolicyTermPtr term, terms()) {
545 0 : bool terminal = term->terminal();
546 0 : bool matched = term->ApplyTerm(route, path, attr);
547 0 : if (matched && terminal) {
548 0 : return std::make_pair(terminal,
549 0 : (*term->actions().begin())->accept());
550 : }
551 0 : }
552 0 : return std::make_pair(false, true);
553 : }
554 :
555 0 : PolicyTerm::PolicyTerm() {
556 0 : }
557 :
558 0 : PolicyTerm::~PolicyTerm() {
559 0 : STLDeleteValues(&actions_);
560 0 : STLDeleteValues(&matches_);
561 0 : }
562 :
563 0 : bool PolicyTerm::terminal() const {
564 0 : if (!actions().empty())
565 0 : return (*actions().begin())->terminal();
566 0 : return false;
567 : }
568 :
569 0 : bool PolicyTerm::ApplyTerm(const BgpRoute *route, const BgpPath *path,
570 : BgpAttr *attr) const {
571 0 : bool matched = true;
572 0 : BOOST_FOREACH(RoutingPolicyMatch *match, matches()) {
573 0 : if (!(*match)(route, path, attr)) {
574 0 : matched = false;
575 0 : break;
576 : }
577 : }
578 0 : if (matched) {
579 0 : bool first = true;
580 0 : BOOST_FOREACH(RoutingPolicyAction *action, actions()) {
581 : // First action defines what to do with the route
582 : // accept/reject/next-term
583 0 : if (first) {
584 0 : if (action->terminal()) {
585 0 : if (!action->accept()) {
586 : // out_attr is unaltered
587 0 : break;
588 : }
589 : }
590 0 : first = false;
591 : } else {
592 0 : RoutingPolicyUpdateAction *update =
593 : static_cast<RoutingPolicyUpdateAction *>(action);
594 0 : (*update)(attr);
595 : }
596 : }
597 : }
598 0 : return matched;
599 : }
600 :
601 : // Compare two terms
602 0 : bool PolicyTerm::operator==(const PolicyTerm &rhs) const {
603 : // Different number of match conditions
604 0 : if (matches().size() != rhs.matches().size()) return false;
605 : // Different number of actions conditions
606 0 : if (actions().size() != rhs.actions().size()) return false;
607 :
608 : // Walk the list of match conditions and compare each match
609 0 : for (MatchList::const_iterator rhs_matches_cit = rhs.matches().begin(),
610 0 : lhs_matches_cit = matches().begin();
611 0 : lhs_matches_cit != matches().end();
612 0 : lhs_matches_cit++, rhs_matches_cit++) {
613 0 : if (**rhs_matches_cit != **lhs_matches_cit)
614 0 : return false;
615 : }
616 :
617 : // Walk the list of actions and compare each action
618 0 : for (ActionList::const_iterator lhs_actions_cit = actions().begin(),
619 0 : rhs_actions_cit = rhs.actions().begin();
620 0 : lhs_actions_cit != actions().end();
621 0 : lhs_actions_cit++, rhs_actions_cit++) {
622 0 : if (**rhs_actions_cit != **lhs_actions_cit)
623 0 : return false;
624 : }
625 0 : return true;
626 : }
|