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"