/* * Copyright (c) 2019-2021 Job Snijders <job@sobornost.net> * Copyright (c) 2007-2019 Alexandre Snarskii <snar@snar.spb.ru> * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #if HAVE_CONFIG_H #include <config.h> #endif #include <sys/types.h> #include <sys/socket.h> #include <ctype.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <strings.h> #include <unistd.h> #include "extern.h" #include "sx_report.h" extern int debug_expander; extern int debug_aggregation; extern int pipelining; extern int expand_special_asn; static int usage(int ecode) { printf("\nUsage: bgpq4 [-h host[:port]] [-S sources] [-E|G|H <num>" "|f <num>|t] [-46ABbdJjKNnpwXz] [-R len] <OBJECTS> ... " "[EXCEPT <OBJECTS> ...]\n"); printf("\nVendor targets:\n"); printf(" no option : Cisco IOS Classic (default)\n"); printf(" -X : Cisco IOS XR\n"); printf(" -U : Huawei\n"); printf(" -u : Huawei XPL\n"); printf(" -j : JSON\n"); printf(" -J : Juniper Junos\n"); printf(" -K : MikroTik RouterOSv6\n"); printf(" -K7 : MikroTik RouterOSv7\n"); printf(" -b : NIC.CZ BIRD\n"); printf(" -N : Nokia SR OS (Classic CLI)\n"); printf(" -n : Nokia SR OS (MD-CLI)\n"); printf(" -n2 : Nokia SR Linux\n"); printf(" -B : OpenBSD OpenBGPD\n"); printf(" -e : Arista EOS\n"); printf(" -F fmt : User defined format (example: '-F %%n/%%l')\n"); printf("\nInput filters:\n"); printf(" -4 : generate IPv4 prefix-lists (default)\n"); printf(" -6 : generate IPv6 prefix-lists\n"); printf(" -m len : maximum prefix length (default: 32 for IPv4, " "128 for IPv6)\n"); printf(" -L depth : limit recursion depth (default: unlimited)\n"), printf(" -S sources: only use specified IRR sources, in the specified " "order (comma separated)\n"); printf(" -w : 'validate' AS numbers: accept only ones with " "registered routes\n"); printf("\nOutput modifiers:\n"); printf(" -3 : assume that your device is asn32-safe (default)\n"); printf(" -A : try to aggregate prefix-lists/route-filters\n"); printf(" -E : generate extended access-list (Cisco), " "route-filter (Juniper)\n" " [ip|ipv6]-prefix-list (Nokia) or prefix-set " "(OpenBGPD)\n"); printf(" -f number : generate input as-path access-list\n"); printf(" -G number : generate output as-path access-list\n"); printf(" -H number : generate origin as-lists (JunOS only)\n"); printf(" -M match : extra match conditions for JunOS route-filters\n"); printf(" -l name : use specified name for generated access/prefix/.." " list\n"); printf(" -p : allow special ASNs like 23456 or in the private range\n"); printf(" -R len : allow more specific routes up to specified masklen\n"); printf(" -r len : allow more specific routes from masklen specified\n"); printf(" -s : generate sequence numbers in prefix-lists (IOS only)\n"); printf(" -t : generate as-sets for OpenBGPD (OpenBGPD 6.4+), BIRD " "and JSON formats\n"); printf(" -z : generate route-filter-list (Junos only)\n"); printf(" -W len : specify max-entries on as-path/as-list line (use 0 for " "infinity)\n"); printf("\nUtility operations:\n"); printf(" -d : generate some debugging output\n"); printf(" -h host : host running IRRD software (default: rr.ntt.net)\n" " use 'host:port' to specify alternate port\n"); printf(" -T : disable pipelining (not recommended)\n"); printf(" -v : print version and exit\n"); printf("\n" PACKAGE_NAME " version: " PACKAGE_VERSION " " "(https://github.com/bgp/bgpq4)\n"); exit(ecode); } static void version(void) { printf(PACKAGE_NAME " - a versatile utility to generate BGP filters\n" "version: " PACKAGE_VERSION "\n" "website: https://github.com/bgp/bgpq4\n" "maintainer: Job Snijders <job@sobornost.net>\n"); exit(0); } static void exclusive(void) { fprintf(stderr,"-E, -F, -K , -f <asnum>, -G <asnum>, and -t are mutually" " exclusive\n"); exit(1); } static void vendor_exclusive(void) { fprintf(stderr, "-b (BIRD), -B (OpenBGPD), -F (formatted), -J (Junos)," " -j (JSON), -K[7] (Microtik ROS), -N (Nokia SR OS Classic)," " -n (Nokia SR OS MD-CLI), -U (Huawei), -u (Huawei XPL)," "-e (Arista) and -X (IOS XR) options are mutually exclusive\n"); exit(1); } static int parseasnumber(struct bgpq_expander *expander, char *asnstr) { char *eon = NULL; expander->asnumber = strtoul(asnstr, &eon, 10); if (expander->asnumber < 1 || expander->asnumber > (65535ul * 65535)) { sx_report(SX_FATAL, "Invalid AS number: %s\n", asnstr); exit(1); } if (eon && *eon == '.') { /* -f 3.3, for example */ uint32_t loas = strtoul(eon + 1, &eon, 10); if (expander->asnumber > 65535) { /* should prevent incorrect numbers like 65537.1 */ sx_report(SX_FATAL,"Invalid AS number: %s\n", asnstr); exit(1); } if (loas < 1 || loas > 65535) { sx_report(SX_FATAL,"Invalid AS number: %s\n", asnstr); exit(1); } if (eon && *eon) { sx_report(SX_FATAL,"Invalid symbol in AS number: " "%c (%s)\n", *eon, asnstr); exit(1); } expander->asnumber=(expander->asnumber << 16) + loas; } else if (eon && *eon) { sx_report(SX_FATAL,"Invalid symbol in AS number: %c (%s)\n", *eon, asnstr); exit(1); } return 0; } int main(int argc, char* argv[]) { int c; struct bgpq_expander expander; int af = AF_INET, selectedipv4 = 0, exceptmode = 0; int widthSet = 0, aggregate = 0, refine = 0, refineLow = 0; unsigned long maxlen = 0; #ifdef HAVE_PLEDGE if (pledge("stdio inet dns", NULL) == -1) { sx_report(SX_ERROR, "pledge() failed"); exit(1); } #endif bgpq_expander_init(&expander, af); if (getenv("IRRD_SOURCES")) expander.sources=getenv("IRRD_SOURCES"); while ((c = getopt(argc, argv, "23467a:AbBdDEeF:S:jJKf:l:L:m:M:NnpW:r:R:G:H:tTh:UuwXsvz")) != EOF) { switch (c) { case '2': if (expander.vendor != V_NOKIA_MD) { sx_report(SX_FATAL, "'2' can only be used after -n\n"); exit(1); } expander.vendor = V_NOKIA_SRL; break; case '3': /* do nothing, 32-bit ASN support is assumed */ break; case '4': /* do nothing, expander already configured for IPv4 */ if (expander.family == AF_INET6) { sx_report(SX_FATAL, "-4 and -6 are mutually " "exclusive\n"); exit(1); } selectedipv4 = 1; break; case '6': if (selectedipv4) { sx_report(SX_FATAL, "-4 and -6 are mutually " "exclusive\n"); exit(1); } af = AF_INET6; expander.family = AF_INET6; expander.tree->family = AF_INET6; break; case '7': if (expander.vendor != V_MIKROTIK6) { sx_report(SX_FATAL, "'7' can only be used after -K\n"); exit(1); } expander.vendor = V_MIKROTIK7; break; case 'a': parseasnumber(&expander, optarg); break; case 'A': if (aggregate) debug_aggregation++; aggregate = 1; break; case 'b': if (expander.vendor) vendor_exclusive(); expander.vendor = V_BIRD; break; case 'B': if (expander.vendor) vendor_exclusive(); expander.vendor = V_OPENBGPD; break; case 'd': debug_expander++; break; case 'E': if (expander.generation) exclusive(); expander.generation = T_EACL; break; case 'e': if (expander.vendor) vendor_exclusive(); expander.vendor = V_ARISTA; expander.sequence = 1; break; case 'F': if (expander.vendor) exclusive(); expander.vendor = V_FORMAT; expander.format = optarg; break; case 'f': if (expander.generation) exclusive(); expander.generation = T_ASPATH; parseasnumber(&expander, optarg); break; case 'G': if (expander.generation) exclusive(); expander.generation = T_OASPATH; parseasnumber(&expander, optarg); break; case 'H': if (expander.generation) exclusive(); expander.generation = T_ASLIST; parseasnumber(&expander, optarg); break; case 'h': { char *d = strchr(optarg, ':'); expander.server = optarg; if (d) { *d = 0; expander.port = d + 1; } } break; case 'J': if (expander.vendor) vendor_exclusive(); expander.vendor = V_JUNIPER; break; case 'j': if (expander.vendor) vendor_exclusive(); expander.vendor = V_JSON; break; case 'K': if (expander.vendor) vendor_exclusive(); expander.vendor = V_MIKROTIK6; break; case 'r': refineLow = strtoul(optarg, NULL, 10); if (!refineLow) { sx_report(SX_FATAL, "Invalid refineLow value:" " %s\n", optarg); exit(1); } break; case 'R': refine = strtoul(optarg, NULL, 10); if (!refine) { sx_report(SX_FATAL,"Invalid refine length:" " %s\n", optarg); exit(1); } break; case 'l': expander.name = optarg; break; case 'L': expander.maxdepth = strtol(optarg, NULL, 10); if (expander.maxdepth < 1) { sx_report(SX_FATAL, "Invalid maximum recursion" " (-L): %s\n", optarg); exit(1); } break; case 'm': maxlen=strtoul(optarg, NULL, 10); if (!maxlen) { sx_report(SX_FATAL, "Invalid maxlen (-m): %s\n", optarg); exit(1); } break; case 'M': { char *mc, *md; expander.match = strdup(optarg); mc = md = expander.match; while (*mc) { if (*mc == '\\') { if (*(mc + 1) == '\n') { *md = '\n'; md++; mc += 2; } else if (*(mc + 1) == 'r') { *md = '\r'; md++; mc += 2; } else if (*(mc + 1) == 't') { *md = '\t'; md++; mc += 2; } else if (*(mc + 1) == '\\') { *md = '\\'; md++; mc += 2; } else { sx_report(SX_FATAL, "Unsupported" " escape \%c (0x%2.2x) in " "'%s'\n", isprint(*mc) ? *mc : 20, *mc, optarg); exit(1); } } else { if (mc != md) { *md = *mc; } md++; mc++; } } *md = 0; } break; case 'N': if (expander.vendor) vendor_exclusive(); expander.vendor = V_NOKIA; break; case 'n': if (expander.vendor) vendor_exclusive(); expander.vendor = V_NOKIA_MD; break; case 'p': expand_special_asn = 1; break; case 't': if (expander.generation) exclusive(); expander.generation = T_ASSET; break; case 'T': pipelining = 0; break; case 's': expander.sequence = 1; break; case 'S': expander.sources = optarg; break; case 'U': if (expander.vendor) exclusive(); expander.vendor = V_HUAWEI; break; case 'u': if (expander.vendor) exclusive(); expander.vendor = V_HUAWEI_XPL; break; case 'W': expander.aswidth = atoi(optarg); if (expander.aswidth < 0) { sx_report(SX_FATAL,"Invalid as-width: %s\n", optarg); exit(1); } widthSet = 1; break; case 'w': expander.validate_asns = 1; break; case 'X': if (expander.vendor) vendor_exclusive(); expander.vendor = V_CISCO_XR; break; case 'v': version(); break; case 'z': if (expander.generation) exclusive(); expander.generation = T_ROUTE_FILTER_LIST; break; default: usage(1); } } argc -= optind; argv += optind; if (!widthSet) { if (expander.generation == T_ASPATH) { int vendor = expander.vendor; switch (vendor) { case V_ARISTA: case V_CISCO: case V_MIKROTIK6: case V_MIKROTIK7: expander.aswidth = 4; break; case V_CISCO_XR: expander.aswidth = 6; break; case V_JUNIPER: case V_NOKIA: case V_NOKIA_MD: case V_NOKIA_SRL: expander.aswidth = 8; break; case V_BIRD: expander.aswidth = 10; break; } } else if (expander.generation == T_OASPATH) { int vendor = expander.vendor; switch (vendor) { case V_ARISTA: case V_CISCO: expander.aswidth = 5; break; case V_CISCO_XR: expander.aswidth = 7; break; case V_JUNIPER: case V_NOKIA: case V_NOKIA_MD: case V_NOKIA_SRL: expander.aswidth = 8; break; } } else if (expander.generation == T_ASLIST) { int vendor = expander.vendor; switch (vendor) { case V_JUNIPER: expander.aswidth = 8; break; } } } if (!expander.generation) expander.generation = T_PREFIXLIST; if (expander.vendor == V_CISCO_XR && expander.generation != T_PREFIXLIST && expander.generation != T_ASPATH && expander.generation != T_OASPATH) { sx_report(SX_FATAL, "Sorry, only prefix-sets and as-paths " "supported for IOS XR\n"); } if (expander.vendor == V_BIRD && expander.generation != T_PREFIXLIST && expander.generation != T_ASPATH && expander.generation != T_ASSET) { sx_report(SX_FATAL, "Sorry, only prefix-lists and as-paths/as-sets " "supported for BIRD output\n"); } if (expander.vendor == V_JSON && expander.generation != T_PREFIXLIST && expander.generation != T_ASPATH && expander.generation != T_ASSET) { sx_report(SX_FATAL, "Sorry, only prefix-lists and as-paths/as-sets " "supported for JSON output\n"); } if (expander.vendor == V_FORMAT && expander.generation != T_PREFIXLIST) sx_report(SX_FATAL, "Sorry, only prefix-lists supported in formatted " "output\n"); if (expander.vendor == V_HUAWEI && expander.generation != T_ASPATH && expander.generation != T_OASPATH && expander.generation != T_PREFIXLIST) sx_report(SX_FATAL, "Sorry, only as-paths and prefix-lists supported " "for Huawei output\n"); if (expander.generation == T_ROUTE_FILTER_LIST && expander.vendor != V_JUNIPER) sx_report(SX_FATAL, "Route-filter-lists (-z) supported for Juniper (-J)" " output only\n"); if (expander.generation == T_ASSET && expander.vendor != V_JSON && expander.vendor != V_OPENBGPD && expander.vendor != V_BIRD) sx_report(SX_FATAL, "As-Sets (-t) supported for JSON (-j), OpenBGPD " "(-B) and BIRD (-b) output only\n"); if (aggregate && expander.vendor == V_JUNIPER && expander.generation == T_PREFIXLIST) { sx_report(SX_FATAL, "Sorry, aggregation (-A) does not work in" " Juniper prefix-lists\nYou can try route-filters (-E) " "or route-filter-lists (-z) instead of prefix-lists\n."); exit(1); } if (aggregate && (expander.vendor == V_NOKIA_MD || expander.vendor == V_NOKIA || expander.vendor == V_NOKIA_SRL) && expander.generation != T_PREFIXLIST) { sx_report(SX_FATAL, "Sorry, aggregation (-A) is not supported with " "ip-prefix-lists (-E) on Nokia.\n"); exit(1); } if (refine && (expander.vendor == V_NOKIA_MD || expander.vendor == V_NOKIA || expander.vendor == V_NOKIA_SRL) && expander.generation != T_PREFIXLIST) { sx_report(SX_FATAL, "Sorry, more-specifics (-R) is not supported with " "ip-prefix-lists (-E) on Nokia.\n"); exit(1); } if (refineLow && (expander.vendor == V_NOKIA_MD || expander.vendor == V_NOKIA || expander.vendor == V_NOKIA_SRL) && expander.generation != T_PREFIXLIST) { sx_report(SX_FATAL, "Sorry, more-specifics (-r) is not supported with " "ip-prefix-lists (-E) on Nokia.\n"); exit(1); } if (aggregate && expander.generation < T_PREFIXLIST) { sx_report(SX_FATAL, "Sorry, aggregation (-A) used only for prefix-" "lists, extended access-lists and route-filters\n"); exit(1); } if (expander.sequence && (expander.vendor != V_CISCO && expander.vendor != V_ARISTA)) { sx_report(SX_FATAL, "Sorry, prefix-lists sequencing (-s) supported" " only for IOS and EOS\n"); exit(1); } if (expander.sequence && expander.generation < T_PREFIXLIST) { sx_report(SX_FATAL, "Sorry, prefix-lists sequencing (-s) can't be " " used for non prefix-list\n"); exit(1); } if (refineLow && !refine) { if (expander.family == AF_INET) refine = 32; else refine = 128; } if (refineLow && refineLow > refine) sx_report(SX_FATAL, "Incompatible values for -r %u and -R %u\n", refineLow, refine); if (refine || refineLow) { if (expander.family == AF_INET6 && refine > 128) { sx_report(SX_FATAL, "Invalid value for refine(-R): %u (1-128 for" " IPv6)\n", refine); } else if (expander.family == AF_INET6 && refineLow > 128) { sx_report(SX_FATAL, "Invalid value for refineLow(-r): %u (1-128 for" " IPv6)\n", refineLow); } else if (expander.family == AF_INET && refine > 32) { sx_report(SX_FATAL, "Invalid value for refine(-R): %u (1-32 for" " IPv4)\n", refine); } else if (expander.family == AF_INET && refineLow > 32) { sx_report(SX_FATAL, "Invalid value for refineLow(-r): %u (1-32 for" " IPv4)\n", refineLow); } if (expander.vendor == V_JUNIPER && expander.generation == T_PREFIXLIST) { if (refine) { sx_report(SX_FATAL, "Sorry, more-specific filters (-R %u) " "is not supported for Juniper prefix-lists.\n" "Use router-filters (-E) or route-filter-lists (-z) " "instead\n", refine); } else { sx_report(SX_FATAL, "Sorry, more-specific filters (-r %u) " "is not supported for Juniper prefix-lists.\n" "Use route-filters (-E) or route-filter-lists (-z) " "instead\n", refineLow); } } if (expander.generation < T_PREFIXLIST) { if (refine) sx_report(SX_FATAL, "Sorry, more-specific filter (-R %u) " "supported only with prefix-list generation\n", refine); else sx_report(SX_FATAL, "Sorry, more-specific filter (-r %u) " "supported only with prefix-list generation\n", refineLow); } } if (maxlen) { if ((expander.family == AF_INET6 && maxlen > 128) || (expander.family == AF_INET && maxlen > 32)) { sx_report(SX_FATAL, "Invalid value for max-prefixlen: %lu (1-128 " "for IPv6, 1-32 for IPv4)\n", maxlen); exit(1); } else if ((expander.family == AF_INET6 && maxlen < 128) || (expander.family == AF_INET && maxlen < 32)) { /* * inet6/128 and inet4/32 does not make sense - all * routes will be accepted, so save some CPU cycles :) */ expander.maxlen = maxlen; } } else if (expander.family == AF_INET) expander.maxlen = 32; else if (expander.family == AF_INET6) expander.maxlen = 128; if (expander.generation == T_EACL && expander.vendor == V_CISCO && expander.family == AF_INET6) { sx_report(SX_FATAL,"Sorry, ipv6 access-lists not supported " "for Cisco yet.\n"); } if (expander.match != NULL && (expander.vendor != V_JUNIPER || expander.generation != T_EACL)) { sx_report(SX_FATAL, "Sorry, extra match conditions (-M) can be used " "only with Juniper route-filters\n"); } if ((expander.generation == T_ASPATH || expander.generation == T_OASPATH || expander.generation == T_ASLIST) && af != AF_INET && !expander.validate_asns) { sx_report(SX_FATAL, "Sorry, -6 makes no sense with as-path (-f/-G) or as-list (-H) " "generation\n"); } if (expander.validate_asns && expander.generation != T_ASPATH && expander.generation != T_OASPATH && expander.generation != T_ASLIST) { sx_report(SX_FATAL, "Sorry, -w makes sense only for as-path " "(-f/-G) generation\n"); } if (!argv[0]) usage(1); while (argv[0]) { char *obj = argv[0]; char *delim = strstr(argv[0], "::"); if (delim) { expander.usesource = 1; obj = delim + 2; } if (!strcmp(argv[0], "EXCEPT")) { exceptmode = 1; } else if (exceptmode) { bgpq_expander_add_stop(&expander, argv[0]); } else if (!strncasecmp(obj, "AS-", 3)) { bgpq_expander_add_asset(&expander, argv[0]); } else if (!strncasecmp(obj, "RS-", 3)) { bgpq_expander_add_rset(&expander, argv[0]); } else if (!strncasecmp(obj, "AS", 2)) { char *ec; if ((ec = strchr(obj, ':'))) { if (!strncasecmp(ec + 1, "AS-", 3)) { bgpq_expander_add_asset(&expander, argv[0]); } else if (!strncasecmp(ec + 1, "RS-", 3)) { bgpq_expander_add_rset(&expander, argv[0]); } else { SX_DEBUG(debug_expander,"Unknown sub-as" " object %s\n", argv[0]); } } else { bgpq_expander_add_as(&expander, argv[0]); } } else { char *ec = strchr(argv[0], '^'); if (!ec && !bgpq_expander_add_prefix(&expander, argv[0])) { sx_report(SX_ERROR, "Unable to add prefix %s " "(bad prefix or address-family)\n", argv[0]); exit(1); } else if (ec && !bgpq_expander_add_prefix_range(&expander, argv[0])) { sx_report(SX_ERROR, "Unable to add prefix-range " "%s (bad range or address-family)\n", argv[0]); exit(1); } } argv++; argc--; } if (!bgpq_expand(&expander)) exit(1); if (refine) sx_radix_tree_refine(expander.tree, refine); if (refineLow) sx_radix_tree_refineLow(expander.tree, refineLow); if (aggregate) sx_radix_tree_aggregate(expander.tree); switch (expander.generation) { case T_NONE: sx_report(SX_FATAL,"Unreachable point"); exit(1); case T_ASPATH: bgpq4_print_aspath(stdout, &expander); break; case T_OASPATH: bgpq4_print_oaspath(stdout, &expander); break; case T_ASLIST: bgpq4_print_aslist(stdout, &expander); break; case T_ASSET: bgpq4_print_asset(stdout, &expander); break; case T_PREFIXLIST: bgpq4_print_prefixlist(stdout, &expander); break; case T_EACL: bgpq4_print_eacl(stdout, &expander); break; case T_ROUTE_FILTER_LIST: bgpq4_print_route_filter_list(stdout, &expander); break; } expander_freeall(&expander); return 0; }