1 /* $OpenBSD: rde_filter.c,v 1.57 2009/08/06 08:53:11 claudio Exp $ */ 2 3 /* 4 * Copyright (c) 2004 Claudio Jeker <claudio@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 #include <sys/types.h> 19 #include <sys/queue.h> 20 21 #include <limits.h> 22 #include <stdlib.h> 23 #include <string.h> 24 25 #include "bgpd.h" 26 #include "rde.h" 27 28 int rde_filter_match(struct filter_rule *, struct rde_aspath *, 29 struct bgpd_addr *, u_int8_t, struct rde_peer *); 30 int filterset_equal(struct filter_set_head *, struct filter_set_head *); 31 32 enum filter_actions 33 rde_filter(u_int16_t ribid, struct rde_aspath **new, struct filter_head *rules, 34 struct rde_peer *peer, struct rde_aspath *asp, struct bgpd_addr *prefix, 35 u_int8_t prefixlen, struct rde_peer *from, enum directions dir) 36 { 37 struct filter_rule *f; 38 enum filter_actions action = ACTION_ALLOW; /* default allow */ 39 40 if (new != NULL) 41 *new = NULL; 42 43 if (asp->flags & F_ATTR_PARSE_ERR) 44 /* 45 * don't try to filter bad updates but let them through 46 * so they act as implicit withdraws 47 */ 48 return (action); 49 50 TAILQ_FOREACH(f, rules, entry) { 51 if (dir != f->dir) 52 continue; 53 if (dir == DIR_IN && f->peer.ribid != ribid) 54 continue; 55 if (f->peer.groupid != 0 && 56 f->peer.groupid != peer->conf.groupid) 57 continue; 58 if (f->peer.peerid != 0 && 59 f->peer.peerid != peer->conf.id) 60 continue; 61 if (rde_filter_match(f, asp, prefix, prefixlen, peer)) { 62 if (asp != NULL && new != NULL) { 63 /* asp may get modified so create a copy */ 64 if (*new == NULL) { 65 *new = path_copy(asp); 66 /* ... and use the copy from now on */ 67 asp = *new; 68 } 69 rde_apply_set(asp, &f->set, prefix->af, 70 from, peer); 71 } 72 if (f->action != ACTION_NONE) 73 action = f->action; 74 if (f->quick) 75 return (action); 76 } 77 } 78 return (action); 79 } 80 81 void 82 rde_apply_set(struct rde_aspath *asp, struct filter_set_head *sh, 83 sa_family_t af, struct rde_peer *from, struct rde_peer *peer) 84 { 85 struct filter_set *set; 86 u_char *np; 87 int as, type; 88 u_int32_t prep_as; 89 u_int16_t nl; 90 u_int8_t prepend; 91 92 if (asp == NULL) 93 return; 94 95 TAILQ_FOREACH(set, sh, entry) { 96 switch (set->type) { 97 case ACTION_SET_LOCALPREF: 98 asp->lpref = set->action.metric; 99 break; 100 case ACTION_SET_RELATIVE_LOCALPREF: 101 if (set->action.relative > 0) { 102 if (set->action.relative + asp->lpref < 103 asp->lpref) 104 asp->lpref = UINT_MAX; 105 else 106 asp->lpref += set->action.relative; 107 } else { 108 if ((u_int32_t)-set->action.relative > 109 asp->lpref) 110 asp->lpref = 0; 111 else 112 asp->lpref += set->action.relative; 113 } 114 break; 115 case ACTION_SET_MED: 116 asp->flags |= F_ATTR_MED | F_ATTR_MED_ANNOUNCE; 117 asp->med = set->action.metric; 118 break; 119 case ACTION_SET_RELATIVE_MED: 120 asp->flags |= F_ATTR_MED | F_ATTR_MED_ANNOUNCE; 121 if (set->action.relative > 0) { 122 if (set->action.relative + asp->med < 123 asp->med) 124 asp->med = UINT_MAX; 125 else 126 asp->med += set->action.relative; 127 } else { 128 if ((u_int32_t)-set->action.relative > 129 asp->med) 130 asp->med = 0; 131 else 132 asp->med += set->action.relative; 133 } 134 break; 135 case ACTION_SET_WEIGHT: 136 asp->weight = set->action.metric; 137 break; 138 case ACTION_SET_RELATIVE_WEIGHT: 139 if (set->action.relative > 0) { 140 if (set->action.relative + asp->weight < 141 asp->weight) 142 asp->weight = UINT_MAX; 143 else 144 asp->weight += set->action.relative; 145 } else { 146 if ((u_int32_t)-set->action.relative > 147 asp->weight) 148 asp->weight = 0; 149 else 150 asp->weight += set->action.relative; 151 } 152 break; 153 case ACTION_SET_PREPEND_SELF: 154 prep_as = rde_local_as(); 155 prepend = set->action.prepend; 156 np = aspath_prepend(asp->aspath, prep_as, prepend, &nl); 157 aspath_put(asp->aspath); 158 asp->aspath = aspath_get(np, nl); 159 free(np); 160 break; 161 case ACTION_SET_PREPEND_PEER: 162 if (from == NULL) 163 break; 164 prep_as = from->conf.remote_as; 165 prepend = set->action.prepend; 166 np = aspath_prepend(asp->aspath, prep_as, prepend, &nl); 167 aspath_put(asp->aspath); 168 asp->aspath = aspath_get(np, nl); 169 free(np); 170 break; 171 case ACTION_SET_NEXTHOP: 172 case ACTION_SET_NEXTHOP_REJECT: 173 case ACTION_SET_NEXTHOP_BLACKHOLE: 174 case ACTION_SET_NEXTHOP_NOMODIFY: 175 case ACTION_SET_NEXTHOP_SELF: 176 nexthop_modify(asp, &set->action.nexthop, set->type, 177 af); 178 break; 179 case ACTION_SET_COMMUNITY: 180 switch (set->action.community.as) { 181 case COMMUNITY_ERROR: 182 case COMMUNITY_ANY: 183 fatalx("rde_apply_set bad community string"); 184 case COMMUNITY_NEIGHBOR_AS: 185 as = peer->conf.remote_as; 186 break; 187 default: 188 as = set->action.community.as; 189 break; 190 } 191 192 switch (set->action.community.type) { 193 case COMMUNITY_ERROR: 194 case COMMUNITY_ANY: 195 fatalx("rde_apply_set bad community string"); 196 case COMMUNITY_NEIGHBOR_AS: 197 type = peer->conf.remote_as; 198 break; 199 default: 200 type = set->action.community.type; 201 break; 202 } 203 204 community_set(asp, as, type); 205 break; 206 case ACTION_DEL_COMMUNITY: 207 switch (set->action.community.as) { 208 case COMMUNITY_ERROR: 209 fatalx("rde_apply_set bad community string"); 210 case COMMUNITY_NEIGHBOR_AS: 211 as = peer->conf.remote_as; 212 break; 213 case COMMUNITY_ANY: 214 default: 215 as = set->action.community.as; 216 break; 217 } 218 219 switch (set->action.community.type) { 220 case COMMUNITY_ERROR: 221 fatalx("rde_apply_set bad community string"); 222 case COMMUNITY_NEIGHBOR_AS: 223 type = peer->conf.remote_as; 224 break; 225 case COMMUNITY_ANY: 226 default: 227 type = set->action.community.type; 228 break; 229 } 230 231 community_delete(asp, as, type); 232 break; 233 case ACTION_PFTABLE: 234 /* convert pftable name to an id */ 235 set->action.id = pftable_name2id(set->action.pftable); 236 set->type = ACTION_PFTABLE_ID; 237 /* FALLTHROUGH */ 238 case ACTION_PFTABLE_ID: 239 pftable_unref(asp->pftableid); 240 asp->pftableid = set->action.id; 241 pftable_ref(asp->pftableid); 242 break; 243 case ACTION_RTLABEL: 244 /* convert the route label to an id for faster access */ 245 set->action.id = rtlabel_name2id(set->action.rtlabel); 246 set->type = ACTION_RTLABEL_ID; 247 /* FALLTHROUGH */ 248 case ACTION_RTLABEL_ID: 249 rtlabel_unref(asp->rtlabelid); 250 asp->rtlabelid = set->action.id; 251 rtlabel_ref(asp->rtlabelid); 252 break; 253 } 254 } 255 } 256 257 int 258 rde_filter_match(struct filter_rule *f, struct rde_aspath *asp, 259 struct bgpd_addr *prefix, u_int8_t plen, struct rde_peer *peer) 260 { 261 int as, type; 262 263 if (asp != NULL && f->match.as.type != AS_NONE) 264 if (aspath_match(asp->aspath, f->match.as.type, 265 f->match.as.as) == 0) 266 return (0); 267 268 if (asp != NULL && f->match.community.as != COMMUNITY_UNSET) { 269 switch (f->match.community.as) { 270 case COMMUNITY_ERROR: 271 fatalx("rde_apply_set bad community string"); 272 case COMMUNITY_NEIGHBOR_AS: 273 as = peer->conf.remote_as; 274 break; 275 default: 276 as = f->match.community.as; 277 break; 278 } 279 280 switch (f->match.community.type) { 281 case COMMUNITY_ERROR: 282 fatalx("rde_apply_set bad community string"); 283 case COMMUNITY_NEIGHBOR_AS: 284 type = peer->conf.remote_as; 285 break; 286 default: 287 type = f->match.community.type; 288 break; 289 } 290 291 if (rde_filter_community(asp, as, type) == 0) 292 return (0); 293 } 294 295 if (f->match.prefix.addr.af != 0) { 296 if (f->match.prefix.addr.af != prefix->af) 297 /* don't use IPv4 rules for IPv6 and vice versa */ 298 return (0); 299 300 if (prefix_compare(prefix, &f->match.prefix.addr, 301 f->match.prefix.len)) 302 return (0); 303 304 /* test prefixlen stuff too */ 305 switch (f->match.prefixlen.op) { 306 case OP_NONE: 307 /* perfect match */ 308 return (plen == f->match.prefix.len); 309 case OP_RANGE: 310 return ((plen >= f->match.prefixlen.len_min) && 311 (plen <= f->match.prefixlen.len_max)); 312 case OP_XRANGE: 313 return ((plen < f->match.prefixlen.len_min) || 314 (plen > f->match.prefixlen.len_max)); 315 case OP_EQ: 316 return (plen == f->match.prefixlen.len_min); 317 case OP_NE: 318 return (plen != f->match.prefixlen.len_min); 319 case OP_LE: 320 return (plen <= f->match.prefixlen.len_min); 321 case OP_LT: 322 return (plen < f->match.prefixlen.len_min); 323 case OP_GE: 324 return (plen >= f->match.prefixlen.len_min); 325 case OP_GT: 326 return (plen > f->match.prefixlen.len_min); 327 } 328 /* NOTREACHED */ 329 } else if (f->match.prefixlen.op != OP_NONE) { 330 /* only prefixlen without a prefix */ 331 332 if (f->match.prefixlen.af != prefix->af) 333 /* don't use IPv4 rules for IPv6 and vice versa */ 334 return (0); 335 336 switch (f->match.prefixlen.op) { 337 case OP_NONE: 338 fatalx("internal filter bug"); 339 case OP_RANGE: 340 return ((plen >= f->match.prefixlen.len_min) && 341 (plen <= f->match.prefixlen.len_max)); 342 case OP_XRANGE: 343 return ((plen < f->match.prefixlen.len_min) || 344 (plen > f->match.prefixlen.len_max)); 345 case OP_EQ: 346 return (plen == f->match.prefixlen.len_min); 347 case OP_NE: 348 return (plen != f->match.prefixlen.len_min); 349 case OP_LE: 350 return (plen <= f->match.prefixlen.len_min); 351 case OP_LT: 352 return (plen < f->match.prefixlen.len_min); 353 case OP_GE: 354 return (plen >= f->match.prefixlen.len_min); 355 case OP_GT: 356 return (plen > f->match.prefixlen.len_min); 357 } 358 /* NOTREACHED */ 359 } 360 361 /* matched somewhen or is anymatch rule */ 362 return (1); 363 } 364 365 int 366 rde_filter_community(struct rde_aspath *asp, int as, int type) 367 { 368 struct attr *a; 369 370 a = attr_optget(asp, ATTR_COMMUNITIES); 371 if (a == NULL) 372 /* no communities, no match */ 373 return (0); 374 375 return (community_match(a->data, a->len, as, type)); 376 } 377 378 int 379 rde_filter_equal(struct filter_head *a, struct filter_head *b, 380 struct rde_peer *peer, enum directions dir) 381 { 382 struct filter_rule *fa, *fb; 383 384 fa = TAILQ_FIRST(a); 385 fb = TAILQ_FIRST(b); 386 387 while (fa != NULL || fb != NULL) { 388 /* skip all rules with wrong direction */ 389 if (fa != NULL && dir != fa->dir) { 390 fa = TAILQ_NEXT(fa, entry); 391 continue; 392 } 393 if (fb != NULL && dir != fb->dir) { 394 fb = TAILQ_NEXT(fb, entry); 395 continue; 396 } 397 398 /* skip all rules with wrong peer */ 399 if (fa != NULL && fa->peer.groupid != 0 && 400 fa->peer.groupid != peer->conf.groupid) { 401 fa = TAILQ_NEXT(fa, entry); 402 continue; 403 } 404 if (fa != NULL && fa->peer.peerid != 0 && 405 fa->peer.peerid != peer->conf.id) { 406 fa = TAILQ_NEXT(fa, entry); 407 continue; 408 } 409 410 if (fb != NULL && fb->peer.groupid != 0 && 411 fb->peer.groupid != peer->conf.groupid) { 412 fb = TAILQ_NEXT(fb, entry); 413 continue; 414 } 415 if (fb != NULL && fb->peer.peerid != 0 && 416 fb->peer.peerid != peer->conf.id) { 417 fb = TAILQ_NEXT(fb, entry); 418 continue; 419 } 420 421 /* compare the two rules */ 422 if ((fa == NULL && fb != NULL) || (fa != NULL && fb == NULL)) 423 /* new rule added or removed */ 424 return (0); 425 426 if (fa->action != fb->action || fa->quick != fb->quick) 427 return (0); 428 if (memcmp(&fa->peer, &fb->peer, sizeof(fa->peer))) 429 return (0); 430 if (memcmp(&fa->match, &fb->match, sizeof(fa->match))) 431 return (0); 432 if (!filterset_equal(&fa->set, &fb->set)) 433 return (0); 434 435 fa = TAILQ_NEXT(fa, entry); 436 fb = TAILQ_NEXT(fb, entry); 437 } 438 return (1); 439 } 440 441 /* free a filterset and take care of possible name2id references */ 442 void 443 filterset_free(struct filter_set_head *sh) 444 { 445 struct filter_set *s; 446 struct nexthop *nh; 447 448 while ((s = TAILQ_FIRST(sh)) != NULL) { 449 TAILQ_REMOVE(sh, s, entry); 450 if (s->type == ACTION_RTLABEL_ID) 451 rtlabel_unref(s->action.id); 452 else if (s->type == ACTION_PFTABLE_ID) 453 pftable_unref(s->action.id); 454 else if (s->type == ACTION_SET_NEXTHOP && 455 bgpd_process == PROC_RDE) { 456 nh = nexthop_get(&s->action.nexthop); 457 --nh->refcnt; 458 (void)nexthop_delete(nh); 459 } 460 free(s); 461 } 462 } 463 464 /* 465 * this function is a bit more complicated than a memcmp() because there are 466 * types that need to be considered equal e.g. ACTION_SET_MED and 467 * ACTION_SET_RELATIVE_MED. Also ACTION_SET_COMMUNITY and ACTION_SET_NEXTHOP 468 * need some special care. It only checks the types and not the values so 469 * it does not do a real compare. 470 */ 471 int 472 filterset_cmp(struct filter_set *a, struct filter_set *b) 473 { 474 if (strcmp(filterset_name(a->type), filterset_name(b->type))) 475 return (a->type - b->type); 476 477 if (a->type == ACTION_SET_COMMUNITY || 478 a->type == ACTION_DEL_COMMUNITY) { /* a->type == b->type */ 479 /* compare community */ 480 if (a->action.community.as - b->action.community.as != 0) 481 return (a->action.community.as - 482 b->action.community.as); 483 return (a->action.community.type - b->action.community.type); 484 } 485 486 if (a->type == ACTION_SET_NEXTHOP && b->type == ACTION_SET_NEXTHOP) { 487 /* 488 * This is the only interesting case, all others are considered 489 * equal. It does not make sense to e.g. set a nexthop and 490 * reject it at the same time. Allow one IPv4 and one IPv6 491 * per filter set or only one of the other nexthop modifiers. 492 */ 493 return (a->action.nexthop.af - b->action.nexthop.af); 494 } 495 496 /* equal */ 497 return (0); 498 } 499 500 int 501 filterset_equal(struct filter_set_head *ah, struct filter_set_head *bh) 502 { 503 struct filter_set *a, *b; 504 const char *as, *bs; 505 506 for (a = TAILQ_FIRST(ah), b = TAILQ_FIRST(bh); 507 a != NULL && b != NULL; 508 a = TAILQ_NEXT(a, entry), b = TAILQ_NEXT(b, entry)) { 509 switch (a->type) { 510 case ACTION_SET_PREPEND_SELF: 511 case ACTION_SET_PREPEND_PEER: 512 if (a->type == b->type && 513 a->action.prepend == b->action.prepend) 514 continue; 515 break; 516 case ACTION_SET_LOCALPREF: 517 case ACTION_SET_MED: 518 case ACTION_SET_WEIGHT: 519 if (a->type == b->type && 520 a->action.metric == b->action.metric) 521 continue; 522 break; 523 case ACTION_SET_RELATIVE_LOCALPREF: 524 case ACTION_SET_RELATIVE_MED: 525 case ACTION_SET_RELATIVE_WEIGHT: 526 if (a->type == b->type && 527 a->action.relative == b->action.relative) 528 continue; 529 break; 530 case ACTION_SET_NEXTHOP: 531 if (a->type == b->type && 532 memcmp(&a->action.nexthop, &b->action.nexthop, 533 sizeof(a->action.nexthop)) == 0) 534 continue; 535 break; 536 case ACTION_SET_NEXTHOP_BLACKHOLE: 537 case ACTION_SET_NEXTHOP_REJECT: 538 case ACTION_SET_NEXTHOP_NOMODIFY: 539 case ACTION_SET_NEXTHOP_SELF: 540 if (a->type == b->type) 541 continue; 542 break; 543 case ACTION_DEL_COMMUNITY: 544 case ACTION_SET_COMMUNITY: 545 if (a->type == b->type && 546 memcmp(&a->action.community, &b->action.community, 547 sizeof(a->action.community)) == 0) 548 continue; 549 break; 550 case ACTION_PFTABLE: 551 case ACTION_PFTABLE_ID: 552 if (b->type == ACTION_PFTABLE) 553 bs = b->action.pftable; 554 else if (b->type == ACTION_PFTABLE_ID) 555 bs = pftable_id2name(b->action.id); 556 else 557 break; 558 559 if (a->type == ACTION_PFTABLE) 560 as = a->action.pftable; 561 else 562 as = pftable_id2name(a->action.id); 563 564 if (strcmp(as, bs) == 0) 565 continue; 566 break; 567 case ACTION_RTLABEL: 568 case ACTION_RTLABEL_ID: 569 if (b->type == ACTION_RTLABEL) 570 bs = b->action.rtlabel; 571 else if (b->type == ACTION_RTLABEL_ID) 572 bs = rtlabel_id2name(b->action.id); 573 else 574 break; 575 576 if (a->type == ACTION_RTLABEL) 577 as = a->action.rtlabel; 578 else 579 as = rtlabel_id2name(a->action.id); 580 581 if (strcmp(as, bs) == 0) 582 continue; 583 break; 584 } 585 /* compare failed */ 586 return (0); 587 } 588 if (a != NULL || b != NULL) 589 return (0); 590 return (1); 591 } 592 593 const char * 594 filterset_name(enum action_types type) 595 { 596 switch (type) { 597 case ACTION_SET_LOCALPREF: 598 case ACTION_SET_RELATIVE_LOCALPREF: 599 return ("localpref"); 600 case ACTION_SET_MED: 601 case ACTION_SET_RELATIVE_MED: 602 return ("metric"); 603 case ACTION_SET_WEIGHT: 604 case ACTION_SET_RELATIVE_WEIGHT: 605 return ("weight"); 606 case ACTION_SET_PREPEND_SELF: 607 return ("prepend-self"); 608 case ACTION_SET_PREPEND_PEER: 609 return ("prepend-peer"); 610 case ACTION_SET_NEXTHOP: 611 case ACTION_SET_NEXTHOP_REJECT: 612 case ACTION_SET_NEXTHOP_BLACKHOLE: 613 case ACTION_SET_NEXTHOP_NOMODIFY: 614 case ACTION_SET_NEXTHOP_SELF: 615 return ("nexthop"); 616 case ACTION_SET_COMMUNITY: 617 return ("community"); 618 case ACTION_DEL_COMMUNITY: 619 return ("community delete"); 620 case ACTION_PFTABLE: 621 case ACTION_PFTABLE_ID: 622 return ("pftable"); 623 case ACTION_RTLABEL: 624 case ACTION_RTLABEL_ID: 625 return ("rtlabel"); 626 } 627 628 fatalx("filterset_name: got lost"); 629 } 630