diff --git a/pierky/arouteserver/commands/__init__.py b/pierky/arouteserver/commands/__init__.py index c8001f12..bad460ef 100644 --- a/pierky/arouteserver/commands/__init__.py +++ b/pierky/arouteserver/commands/__init__.py @@ -14,7 +14,7 @@ # along with this program. If not, see . from .tpl_rendering import HTMLCommand, DumpTemplateContextCommand, \ - BIRDCommand, OpenBGPDCommand, BuildCommand + BIRDCommand, OpenBGPDCommand, BuildCommand, JunosDCommand from .check_new_release import CheckNewRelease from .clients_from_peeringdb import ClientsFromPeeringDBCommand from .clients_from_euroix import ClientsFromEuroIXCommand @@ -30,6 +30,7 @@ BuildCommand, BIRDCommand, OpenBGPDCommand, + JunosDCommand, HTMLCommand, DumpTemplateContextCommand, ClientsFromPeeringDBCommand, diff --git a/pierky/arouteserver/commands/configure.py b/pierky/arouteserver/commands/configure.py index 3809ef25..b2ae2bb2 100644 --- a/pierky/arouteserver/commands/configure.py +++ b/pierky/arouteserver/commands/configure.py @@ -138,7 +138,7 @@ def collect_answers(self): ) self.add_answer("daemon", self.ask.ask, "Which BGP daemon will be used?", - options=["bird", "openbgpd"] + options=["bird", "openbgpd", "junos"] ) if self.answers["daemon"] == "openbgpd": diff --git a/pierky/arouteserver/commands/junos.py b/pierky/arouteserver/commands/junos.py new file mode 100644 index 00000000..075bf839 --- /dev/null +++ b/pierky/arouteserver/commands/junos.py @@ -0,0 +1,111 @@ +# Copyright (C) 2017-2019 Pier Carlo Chiodi +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import sys +import yaml + +from .base import ARouteServerCommand + +from ..config.base import convert_deprecated +from ..config.general import ConfigParserGeneral +from ..config.program import program_config + +class ShowConfigCommand(ARouteServerCommand): + + NEEDS_CONFIG = True + + COMMAND_NAME = "junos" + COMMAND_HELP = ("Create configurations for junos") + + @classmethod + def add_arguments(cls, parser): + super(ShowConfigCommand, cls).add_arguments(parser) + + parser.add_argument( + "--junos", + help="General route server configuration file.", + metavar="FILE", + dest="cfg_general") + + def run(self): + current_config_path = program_config.get("cfg_general") + self.show_config(current_config_path, sys.stdout) + + @staticmethod + def show_config(current_config_path, output): + + def wr_line(configured, level, line): + if configured is True: + status = "configured" + elif configured is False: + status = "default" + else: + status = "" + + output.write("{status:<15} {indent}{line}\n".format( + status=status, + indent=" " * level, + line=line + )) + + with open(current_config_path, "r") as f: + current_config = yaml.safe_load(f) + convert_deprecated(current_config["cfg"]) + + distrib = ConfigParserGeneral() + distrib._load_from_yaml("cfg:\n" + " rs_as: 65534\n" + " router_id: 192.0.2.1\n") + distrib.parse() + del distrib["communities"] + del distrib["custom_communities"] + + ordered_schema = ConfigParserGeneral.get_schema() + del ordered_schema["cfg"]["communities"] + del ordered_schema["cfg"]["custom_communities"] + + def get_val_repr(val): + if isinstance(val, dict): + return ", ".join("{}: {}".format(k, v) for k, v in sorted(val.items())) + else: + return str(val).strip() + + def iterate(ordered_schema, distrib, current, level=0): + for k in ordered_schema: + if current is distrib: + configured = False + else: + configured = k in current + + iterate_over = current if configured else distrib + + if isinstance(ordered_schema[k], dict): + + wr_line(None, level, k + ":") + iterate(ordered_schema[k], distrib[k], iterate_over[k], + level + 1) + + elif isinstance(ordered_schema[k], list) or \ + isinstance(iterate_over[k], list): + + wr_line(configured, level, k + ":") + for v in iterate_over[k]: + wr_line(configured, level, " - " + get_val_repr(v)) + + else: + v = get_val_repr(iterate_over[k]) + wr_line(configured, level, "{}: {}".format(k, v)) + + iterate(ordered_schema, distrib.cfg, current_config) diff --git a/pierky/arouteserver/commands/tpl_rendering.py b/pierky/arouteserver/commands/tpl_rendering.py index 058ab758..dcba3308 100644 --- a/pierky/arouteserver/commands/tpl_rendering.py +++ b/pierky/arouteserver/commands/tpl_rendering.py @@ -307,6 +307,16 @@ class OpenBGPDCommand(ConfigRenderingCommand): def _get_template_sub_dir(self): return "openbgpd" +class JunosDCommand(ConfigRenderingCommand): + + COMMAND_NAME = "junos" + COMMAND_HELP = "Junos route server configuration for Juniper devices." + + BUILDER_CLASS = OpenBGPDConfigBuilder + + def _get_template_sub_dir(self): + return "junos" + class HTMLCommand(TemplateRenderingCommands): COMMAND_NAME = "html" diff --git a/templates/junos/community.j2 b/templates/junos/community.j2 new file mode 100644 index 00000000..a06a8096 --- /dev/null +++ b/templates/junos/community.j2 @@ -0,0 +1,28 @@ + {%- set prepend_once = cfg.communities.prepend_once_to_peer.std.split(":")[0] %} + {%- set prepend_twice = cfg.communities.prepend_twice_to_peer.std.split(":")[0] %} + {%- set prepend_thrice = cfg.communities.prepend_thrice_to_peer.std.split(":")[0] %} + community GRACEFUL_SHUTDOWN members 65535:0; + community WELL-KNOWN members [ no-advertise no-export ]; + community NO-ADVERTISE 65535:65281; + community NO-EXPORT 65535:65282; + + community REDISTRIBUTE-ALL members [{{ cfg.rs_as }}:{{ cfg.rs_as }} target:{{ cfg.rs_as }}:{{ cfg.rs_as }} {{ cfg.rs_as }}:1:0 ]; + community NO-REDISTRIBUTE members [0:{{ cfg.rs_as }} target:0:{{ cfg.rs_as }} {{ cfg.rs_as }}:0:0 ]; + community PREPEND-ONCE-ALL members [{{ prepend_once }}:0 target:{{ prepend_once }}:0 {{ cfg.rs_as }}:101:0 ]; + community PREPEND-TWICE-ALL members [{{ prepend_twice }}:0 target:{{ prepend_twice }}:0 {{ cfg.rs_as }}:102:0 ]; + community PREPEND-TRICE-ALL members [{{ prepend_thrice }}:0 target:{{ prepend_thrice }}:0 {{ cfg.rs_as }}:103:0 ]; + +{% for client in clients %} + community BLOCK-TO-{{ client.id }} members [0:{{ client.asn }} target:0:{{ client.asn }} 6695:0:{{ client.asn }}]; + community PERMIT-TO-{{ client.id }} members [{{ cfg.rs_as }}:{{ client.asn }} target:{{ cfg.rs_as }}:{{ client.asn }} {{ cfg.rs_as }}:1:{{ client.asn }}]; + community PREPEND-ONCE-{{ client.id }} members [{{ prepend_once }}:{{ client.asn }} target:{{ prepend_once }}:{{ client.asn }} {{ cfg.rs_as }}:101:{{ client.asn }}]; + community PREPEND-TWICE-{{ client.id }} members [{{ prepend_twice }}:{{ client.asn }} target:{{ prepend_twice }}:{{ client.asn }} {{ cfg.rs_as }}:102:{{ client.asn }}]; + community PREPEND-TRICE-{{ client.id }} members [{{ prepend_thrice }}:{{ client.asn }} target:{{ prepend_thrice }}:{{ client.asn }} {{ cfg.rs_as }}:103:{{ client.asn }}]; + community NO-ADVERTISE-{{ client.id }} members {{ cfg.rs_as }}:902:{{ client.asn }}; + community NO-EXPORT-{{ client.id }} members {{ cfg.rs_as }}:901:{{ client.asn }}; + +{% endfor %} + + community origin-validation-state-invalid members 0x4300:0.0.0.0:2; + community origin-validation-state-unknown members 0x4300:0.0.0.0:1; + community origin-validation-state-valid members 0x4300:0.0.0.0:0; \ No newline at end of file diff --git a/templates/junos/header.j2 b/templates/junos/header.j2 new file mode 100644 index 00000000..6eede883 --- /dev/null +++ b/templates/junos/header.j2 @@ -0,0 +1,4 @@ +routing-options { + autonomous-system {{ cfg.rs_as }} + router-id {{ cfg.router_id }} +} diff --git a/templates/junos/main.j2 b/templates/junos/main.j2 new file mode 100644 index 00000000..62f83f86 --- /dev/null +++ b/templates/junos/main.j2 @@ -0,0 +1,8 @@ +# built by ARouteServer +{{ "header"|include_local_file -}} + +{% include "header.j2" %} + +{% include "policy-options.j2" %} + +{% include "routing-instance.j2" %} \ No newline at end of file diff --git a/templates/junos/policy-options.j2 b/templates/junos/policy-options.j2 new file mode 100644 index 00000000..df9328d8 --- /dev/null +++ b/templates/junos/policy-options.j2 @@ -0,0 +1,36 @@ +policy-options { + + {% include "community.j2" %} + + prefix-list BOGONS_v4 { + {% for entry in bogons %} + {{ entry.prefix }}{{ "/{}".format(entry.length) if entry.length > 0 else ""}}; + {% endfor %} + } + prefix-list TOO-MANY-HOPS { + apply-path ".{50,}"; + } + prefix-list EBGP_PEERS { + apply-path "routing-instances <*> protocols bgp group <*> neighbor <*>"; + } + as-path-group BOGON-ASNs { + /* RFC7607 */ + as-path zero ".* 0 .*"; + /* RFC 4893 AS_TRANS */ + as-path as_trans ".* 23456 .*"; + /* RFC 5398 and documentation/example ASNs */ + as-path examples1 ".* [64496-64511] .*"; + as-path examples2 ".* [65536-65551] .*"; + /* RFC 6996 Private ASNs*/ + as-path reserved1 ".* [64512-65534] .*"; + as-path reserved2 ".* [4200000000-4294967294] .*"; + /* RFC 6996 Last 16 and 32 bit ASNs */ + as-path last16 ".* 65535 .*"; + as-path last32 ".* 4294967295 .*"; + /* RFC IANA reserved ASNs*/ + as-path iana-reserved ".* [65552-131071] .*"; + } + + {% include "policy-statement.j2" %} + +} \ No newline at end of file diff --git a/templates/junos/policy-statement.j2 b/templates/junos/policy-statement.j2 new file mode 100644 index 00000000..70090aff --- /dev/null +++ b/templates/junos/policy-statement.j2 @@ -0,0 +1,109 @@ +## Common policy statements begins ## + +policy-statement ALLOW-GRACEFUL-SHUTDOWN { + term 1 { + from { + protocol bgp; + community GRACEFUL_SHUTDOWN; + } + then { + local-preference 0; + next policy; + } + } +} +policy-statement BGP-IMPORT-REJECTv4 { + term bogon-asns { + from as-path-group BOGON-ASNs; + then reject; + } + term bogon-ipv4 { + from { + prefix-list BOGONS_v4; + } + then reject; + } + term reject_too_small_prefixes_v4 { + from { + route-filter 0.0.0.0/0 prefix-length-range /25-/32; + } + then reject; + } + term no-long-paths { + from as-path TOO-MANY-HOPS; + then reject; + } + term reject-rpki-invalid + from { + protocol bgp; + validation-database invalid; + } + then { + validation-state invalid; + reject; + } + } +} +policy-statement NO-REDISTRIBUTE { + term 0 { + from community NO-REDISTRIBUTE; + then reject; + } + term 1 { + from community WELL-KNOWN; + then reject; + } +} +policy-statement IMPORT-ALL-RIBS { + term to-all-ribs { + from community REDISTRIBUTE-ALL; + then accept; + } +} +policy-statement NO-ADVERTISE-NO-EXPORT { + term set-no-advertise { + from community NO-ADVERTISE; + then set community add no-advertise; + } + term set-no-export { + from community NO-EXPORT; + then set community add no-export; + } +} +## Common policy statements ends ## + +{% for client in clients %} +policy-statement IMPORT-{{ client.id }}-RIB { + term block-this-rib { + from community BLOCK-TO-{{ client.id }}; + then reject; + } + term permit-this-rib { + from community PERMIT-TO-{{ client.id }}; + then accept; + } + term no-advertise-this-rib { + from community NO-ADVERTISE-{{ client.id }}; + then community add no-advertise; + } + term no-export-this-rib { + from community NO-EXPORT-{{ client.id }}; + then community add no-export; + } +} + +policy-statement EXPORT-ASPATH-PADDING-{{ client.id }} { + term prepend-once-all { + from community [ PREPEND-ONCE-ALL PREPEND-ONCE-{{ client.id }} ]; + then as-path-prepend {{ client.asn }}; + } + term prepend-twice-all { + from community [ PREPEND-TWICE-ALL PREPEND-TWICE-{{ client.id }} ]; + then as-path-prepend "{{ client.asn }} {{ client.asn }}"; + } + term prepend-trice-all { + from community [ PREPEND-TRICE-ALL PREPEND-TRICE-{{ client.id }}]; + then as-path-prepend "{{ client.asn }} {{ client.asn }} {{ client.asn }}"; + } +} +{% endfor %} \ No newline at end of file diff --git a/templates/junos/routing-instance.j2 b/templates/junos/routing-instance.j2 new file mode 100644 index 00000000..870c9743 --- /dev/null +++ b/templates/junos/routing-instance.j2 @@ -0,0 +1,23 @@ +{% for client in clients %} +routing-instances { + CLIENT1_{{ client.id }} { + instance-type no-forwarding; + routing-options { + router-id {{ cfg.router_id }}; + instance-import [ IPv4-ONLY IMPORT-{{ client.id }}-RIB IMPORT-ALL-RIBS ]; + } + protocols { + bgp { + peer-as {{ client.asn }}; + group CLIENT-{{ client.id }} { + export EXPORT-ASPATH-PADDING-{{ client.id }} + neighbor {{ client.ip }} { + authentication-key Sup3rS3cr3t-{{ client.asn }}; + forwarding-context master; + } + } + } + } + } +} +{% endfor %} \ No newline at end of file diff --git a/tests/cli b/tests/cli index 0304233d..7a0dcf62 100755 --- a/tests/cli +++ b/tests/cli @@ -192,6 +192,9 @@ build_cmd "openbgpd" --target-version 6.1 | must_not_contain "Compatibility issu SUB_TEST="$LINENO" build_cmd "openbgpd" --ignore-issues "blackhole_filtering_rewrite_ipv6_nh" | must_not_contain "'do_not_announce_to_peer'" +SUB_TEST="$LINENO" +build_cmd "junos" | must_not_contain "invalid choice" + # --------------------------------------------- reset TITLE="OpenBGPD large comms"