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