summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile5
-rw-r--r--data/templates/frr/daemons.frr.tmpl2
-rw-r--r--data/templates/frr/eigrpd.frr.j221
-rw-r--r--data/templates/frr/policy.frr.j228
-rw-r--r--data/templates/frr/ripd.frr.j26
-rw-r--r--data/templates/sla/owamp-override.conf.j216
-rw-r--r--data/templates/sla/owamp-server.conf.j220
-rw-r--r--data/templates/sla/twamp-override.conf.j216
-rw-r--r--data/templates/sla/twamp-server.conf.j218
-rw-r--r--debian/control4
-rw-r--r--interface-definitions/include/eigrp/protocol-common-config.xml.i121
-rw-r--r--interface-definitions/include/rip/access-list.xml.i (renamed from interface-definitions/include/rip/rip-access-list.xml.i)2
-rw-r--r--interface-definitions/include/rip/access-list6.xml.i (renamed from interface-definitions/include/rip/rip-access-list6.xml.i)2
-rw-r--r--interface-definitions/include/rip/default-information.xml.i (renamed from interface-definitions/include/rip/rip-default-information.xml.i)2
-rw-r--r--interface-definitions/include/rip/default-metric.xml.i (renamed from interface-definitions/include/rip/rip-default-metric.xml.i)2
-rw-r--r--interface-definitions/include/rip/interface.xml.i (renamed from interface-definitions/include/rip/rip-interface.xml.i)2
-rw-r--r--interface-definitions/include/rip/prefix-list.xml.i (renamed from interface-definitions/include/rip/rip-prefix-list.xml.i)2
-rw-r--r--interface-definitions/include/rip/prefix-list6.xml.i (renamed from interface-definitions/include/rip/rip-prefix-list6.xml.i)2
-rw-r--r--interface-definitions/include/rip/redistribute.xml.i (renamed from interface-definitions/include/rip/rip-redistribute.xml.i)2
-rw-r--r--interface-definitions/include/rip/timers.xml.i (renamed from interface-definitions/include/rip/rip-timers.xml.i)2
-rw-r--r--interface-definitions/include/rip/version.xml.i18
-rw-r--r--interface-definitions/include/version/policy-version.xml.i2
-rw-r--r--interface-definitions/pki.xml.in1
-rw-r--r--interface-definitions/policy.xml.in126
-rw-r--r--interface-definitions/protocols-eigrp.xml.in17
-rw-r--r--interface-definitions/protocols-rip.xml.in62
-rw-r--r--interface-definitions/protocols-ripng.xml.in26
-rw-r--r--interface-definitions/protocols-static.xml.in2
-rw-r--r--interface-definitions/service_sla.xml.in36
-rw-r--r--interface-definitions/vrf.xml.in11
-rw-r--r--op-mode-definitions/force-wamp.xml.in25
-rw-r--r--op-mode-definitions/ipv6-route.xml.in30
-rw-r--r--op-mode-definitions/pki.xml.in135
-rw-r--r--op-mode-definitions/show-arp.xml.in4
-rw-r--r--op-mode-definitions/show-ip.xml.in24
-rw-r--r--op-mode-definitions/show-ipv6.xml.in2
-rw-r--r--python/vyos/frr.py2
-rw-r--r--python/vyos/pki.py2
-rw-r--r--python/vyos/util.py17
-rw-r--r--smoketest/configs/bgp-small-internet-exchange8
-rwxr-xr-xsmoketest/scripts/cli/test_load_balancning_wan.py257
-rwxr-xr-xsmoketest/scripts/cli/test_pki.py57
-rwxr-xr-xsmoketest/scripts/cli/test_policy.py82
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_rip.py15
-rwxr-xr-xsrc/conf_mode/pki.py129
-rwxr-xr-xsrc/conf_mode/policy.py10
-rwxr-xr-xsrc/conf_mode/protocols_eigrp.py123
-rwxr-xr-xsrc/conf_mode/protocols_rip.py2
-rwxr-xr-xsrc/conf_mode/service_sla.py113
-rwxr-xr-xsrc/migration-scripts/policy/2-to-358
-rwxr-xr-xsrc/op_mode/pki.py201
-rwxr-xr-xsrc/op_mode/show_neigh.py162
52 files changed, 1839 insertions, 195 deletions
diff --git a/Makefile b/Makefile
index 2333eebed..5e01108c0 100644
--- a/Makefile
+++ b/Makefile
@@ -37,6 +37,11 @@ interface_definitions: $(config_xml_obj)
rm -rf $(TMPL_DIR)/qos
rm -rf $(TMPL_DIR)/interfaces/input
+ # T2472 - EIGRP support
+ rm -rf $(TMPL_DIR)/protocols/eigrp
+ # T2773 - EIGRP support for VRF
+ rm -rf $(TMPL_DIR)/vrf/name/node.tag/protocols/eigrp
+
# XXX: test if there are empty node.def files - this is not allowed as these
# could mask help strings or mandatory priority statements
find $(TMPL_DIR) -name node.def -type f -empty -exec false {} + || sh -c 'echo "There are empty node.def files! Check your interface definitions." && exit 1'
diff --git a/data/templates/frr/daemons.frr.tmpl b/data/templates/frr/daemons.frr.tmpl
index ab7b14d6b..df98e74d6 100644
--- a/data/templates/frr/daemons.frr.tmpl
+++ b/data/templates/frr/daemons.frr.tmpl
@@ -8,7 +8,7 @@ isisd=yes
pimd=no
ldpd=yes
nhrpd=no
-eigrpd=no
+eigrpd=yes
babeld=no
sharpd=no
pbrd=no
diff --git a/data/templates/frr/eigrpd.frr.j2 b/data/templates/frr/eigrpd.frr.j2
new file mode 100644
index 000000000..67f8a3ad1
--- /dev/null
+++ b/data/templates/frr/eigrpd.frr.j2
@@ -0,0 +1,21 @@
+!
+router eigrp {{ local_as }} {{ 'vrf ' ~ vrf if vrf is vyos_defined }}
+{% if maximum_paths is vyos_defined %}
+maximum-paths {{ maximum_paths }}
+{% endif %}
+{% if metric.weights is vyos_defined %}
+metric weights {{ metric.weights }}
+{% endif %}
+{% if network is vyos_defined %}
+{% for net in network %}
+network {{ net }}
+{% endfor %}
+{% endif %}
+{% if redistribute is vyos_defined %}
+{% for protocol in redistribute %}
+redistribute {{ protocol }}
+{% endfor %}
+{% endif %}
+{% if variance is vyos_defined %}
+variance {{ variance }}
+{% endif %} \ No newline at end of file
diff --git a/data/templates/frr/policy.frr.j2 b/data/templates/frr/policy.frr.j2
index a42b73e98..33df17770 100644
--- a/data/templates/frr/policy.frr.j2
+++ b/data/templates/frr/policy.frr.j2
@@ -185,12 +185,24 @@ route-map {{ route_map }} {{ rule_config.action }} {{ rule }}
{% if rule_config.match.ip.address.prefix_list is vyos_defined %}
match ip address prefix-list {{ rule_config.match.ip.address.prefix_list }}
{% endif %}
+{% if rule_config.match.ip.address.prefix_len is vyos_defined %}
+ match ip address prefix-len {{ rule_config.match.ip.address.prefix_len }}
+{% endif %}
{% if rule_config.match.ip.nexthop.access_list is vyos_defined %}
match ip next-hop {{ rule_config.match.ip.nexthop.access_list }}
{% endif %}
+{% if rule_config.match.ip.nexthop.address is vyos_defined %}
+ match ip next-hop address {{ rule_config.match.ip.nexthop.address }}
+{% endif %}
+{% if rule_config.match.ip.nexthop.prefix_len is vyos_defined %}
+ match ip next-hop prefix-len {{ rule_config.match.ip.nexthop.prefix_len }}
+{% endif %}
{% if rule_config.match.ip.nexthop.prefix_list is vyos_defined %}
match ip next-hop prefix-list {{ rule_config.match.ip.nexthop.prefix_list }}
{% endif %}
+{% if rule_config.match.ip.nexthop.type is vyos_defined %}
+ match ip next-hop type {{ rule_config.match.ip.nexthop.type }}
+{% endif %}
{% if rule_config.match.ip.route_source.access_list is vyos_defined %}
match ip route-source {{ rule_config.match.ip.route_source.access_list }}
{% endif %}
@@ -203,8 +215,20 @@ route-map {{ route_map }} {{ rule_config.action }} {{ rule }}
{% if rule_config.match.ipv6.address.prefix_list is vyos_defined %}
match ipv6 address prefix-list {{ rule_config.match.ipv6.address.prefix_list }}
{% endif %}
-{% if rule_config.match.ipv6.nexthop is vyos_defined %}
- match ipv6 next-hop address {{ rule_config.match.ipv6.nexthop }}
+{% if rule_config.match.ipv6.address.prefix_len is vyos_defined %}
+ match ipv6 address prefix-len {{ rule_config.match.ipv6.address.prefix_len }}
+{% endif %}
+{% if rule_config.match.ipv6.nexthop.address is vyos_defined %}
+ match ipv6 next-hop address {{ rule_config.match.ipv6.nexthop.address }}
+{% endif %}
+{% if rule_config.match.ipv6.nexthop.access_list is vyos_defined %}
+ match ipv6 next-hop {{ rule_config.match.ipv6.nexthop.access_list }}
+{% endif %}
+{% if rule_config.match.ipv6.nexthop.prefix_list is vyos_defined %}
+ match ipv6 next-hop prefix-list {{ rule_config.match.ipv6.nexthop.prefix_list }}
+{% endif %}
+{% if rule_config.match.ipv6.nexthop.type is vyos_defined %}
+ match ipv6 next-hop type {{ rule_config.match.ipv6.nexthop.type }}
{% endif %}
{% if rule_config.match.large_community.large_community_list is vyos_defined %}
match large-community {{ rule_config.match.large_community.large_community_list }}
diff --git a/data/templates/frr/ripd.frr.j2 b/data/templates/frr/ripd.frr.j2
index e884ed85f..e9e484cc2 100644
--- a/data/templates/frr/ripd.frr.j2
+++ b/data/templates/frr/ripd.frr.j2
@@ -32,6 +32,12 @@ interface {{ iface }}
{% if iface_config.split_horizon.poison_reverse is vyos_defined %}
ip rip split-horizon poisoned-reverse
{% endif %}
+{% if iface_config.receive.version is vyos_defined %}
+ ip rip receive version {{ iface_config.receive.version }}
+{% endif %}
+{% if iface_config.send.version is vyos_defined %}
+ ip rip send version {{ iface_config.send.version }}
+{% endif %}
exit
!
{% endfor %}
diff --git a/data/templates/sla/owamp-override.conf.j2 b/data/templates/sla/owamp-override.conf.j2
new file mode 100644
index 000000000..b5ec161d4
--- /dev/null
+++ b/data/templates/sla/owamp-override.conf.j2
@@ -0,0 +1,16 @@
+[Unit]
+Description==OWAMP server
+After=vyos-router.service
+# Only start if there is a configuration file
+ConditionFileNotEmpty=/etc/owamp-server/owamp-server.conf
+
+[Service]
+KillMode=process
+Type=simple
+ExecStart=/usr/sbin/owampd -c /etc/owamp-server -R /var/run
+ExecReload=/bin/kill -HUP $MAINPID
+PIDFile=/run/owamp-server.pid
+LimitNOFILE=4096
+
+[Install]
+WantedBy=multi-user.target
diff --git a/data/templates/sla/owamp-server.conf.j2 b/data/templates/sla/owamp-server.conf.j2
new file mode 100644
index 000000000..6af963e57
--- /dev/null
+++ b/data/templates/sla/owamp-server.conf.j2
@@ -0,0 +1,20 @@
+### Autogenerated by service_twamp-server.py ###
+
+user owamp
+group owamp
+
+verbose
+vardir /var/run
+
+# location for "recv" session files.
+# The "catalog" subdirectory is completely cleaned and recreated each time
+datadir /var/lib/owamp
+
+srcnode :{{ port }}
+
+# This is used to limit testing to a specific port range. The valid values are:
+# 0 (twampd will let the system to pick the port number (ephemeral)
+# low-high (A range. high must be larger than low.)
+testports 8760-9960
+
+diskfudge 3.0
diff --git a/data/templates/sla/twamp-override.conf.j2 b/data/templates/sla/twamp-override.conf.j2
new file mode 100644
index 000000000..34bbd228b
--- /dev/null
+++ b/data/templates/sla/twamp-override.conf.j2
@@ -0,0 +1,16 @@
+[Unit]
+Description==TWAMP server
+After=vyos-router.service
+# Only start if there is a configuration file
+ConditionFileNotEmpty=/etc/twamp-server/twamp-server.conf
+
+[Service]
+KillMode=process
+Type=simple
+ExecStart=/usr/sbin/twampd -c /etc/twamp-server -R /var/run
+ExecReload=/bin/kill -HUP $MAINPID
+PIDFile=/run/twamp-server.pid
+LimitNOFILE=4096
+
+[Install]
+WantedBy=multi-user.target
diff --git a/data/templates/sla/twamp-server.conf.j2 b/data/templates/sla/twamp-server.conf.j2
new file mode 100644
index 000000000..ea5bbb54a
--- /dev/null
+++ b/data/templates/sla/twamp-server.conf.j2
@@ -0,0 +1,18 @@
+### Autogenerated by service_twamp-server.py ###
+
+user twamp
+group twamp
+
+verbose
+vardir /var/run
+
+# location for "recv" session files.
+# The "catalog" subdirectory is completely cleaned and recreated each time
+datadir /var/lib/twamp
+
+srcnode :{{ port }}
+
+# This is used to limit testing to a specific port range. The valid values are:
+# 0 (twampd will let the system to pick the port number (ephemeral)
+# low-high (A range. high must be larger than low.)
+testports 18760-19960
diff --git a/debian/control b/debian/control
index 437879764..6a6ccf602 100644
--- a/debian/control
+++ b/debian/control
@@ -108,6 +108,8 @@ Depends:
openvpn-auth-ldap,
openvpn-auth-radius,
openvpn-otp,
+ owamp-client,
+ owamp-server,
pciutils,
pdns-recursor,
pmacct (>= 1.6.0),
@@ -158,6 +160,8 @@ Depends:
tftpd-hpa,
traceroute,
tuned,
+ twamp-client,
+ twamp-server,
udp-broadcast-relay,
uidmap,
usb-modeswitch,
diff --git a/interface-definitions/include/eigrp/protocol-common-config.xml.i b/interface-definitions/include/eigrp/protocol-common-config.xml.i
new file mode 100644
index 000000000..147277102
--- /dev/null
+++ b/interface-definitions/include/eigrp/protocol-common-config.xml.i
@@ -0,0 +1,121 @@
+<!-- include start from eigrp/protocol-common-config.xml.i -->
+<leafNode name="local-as">
+ <properties>
+ <help>Autonomous System Number (ASN)</help>
+ <valueHelp>
+ <format>u32:1-65535</format>
+ <description>Autonomous System Number</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ </properties>
+</leafNode>
+<leafNode name="maximum-paths">
+ <properties>
+ <help>Forward packets over multiple paths</help>
+ <valueHelp>
+ <format>u32:1-32</format>
+ <description>Number of paths</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-32"/>
+ </constraint>
+ </properties>
+</leafNode>
+<node name="metric">
+ <properties>
+ <help>Modify metrics and parameters for advertisement</help>
+ </properties>
+ <children>
+ <leafNode name="weights">
+ <properties>
+ <help>Modify metric coefficients</help>
+ <valueHelp>
+ <format>u32:0-255</format>
+ <description>K1</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-255"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<leafNode name="network">
+ <properties>
+ <help>Enable routing on an IP network</help>
+ <valueHelp>
+ <format>ipv4net</format>
+ <description>EIGRP network prefix</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ip-prefix"/>
+ </constraint>
+ <multi/>
+ </properties>
+</leafNode>
+<leafNode name="passive-interface">
+ <properties>
+ <help>Suppress routing updates on an interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces.py</script>
+ </completionHelp>
+ </properties>
+</leafNode>
+<leafNode name="redistribute">
+ <properties>
+ <help>Redistribute information from another routing protocol</help>
+ <valueHelp>
+ <format>bgp</format>
+ <description>Border Gateway Protocol (BGP)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>connected</format>
+ <description>Connected routes</description>
+ </valueHelp>
+ <valueHelp>
+ <format>nhrp</format>
+ <description>Next Hop Resolution Protocol (NHRP)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ospf</format>
+ <description>Open Shortest Path First (OSPFv2)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>rip</format>
+ <description>Routing Information Protocol (RIP)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>static</format>
+ <description>Statically configured routes</description>
+ </valueHelp>
+ <valueHelp>
+ <format>vnc</format>
+ <description>Virtual Network Control (VNC)</description>
+ </valueHelp>
+ <completionHelp>
+ <list>bgp connected nhrp ospf rip static vnc</list>
+ </completionHelp>
+ <constraint>
+ <regex>(bgp|connected|nhrp|ospf|rip|static|vnc)</regex>
+ </constraint>
+ <multi/>
+ </properties>
+</leafNode>
+#include <include/route-map.xml.i>
+#include <include/router-id.xml.i>
+<!-- FRR timers not implemented yet -->
+<leafNode name="variance">
+ <properties>
+ <help>Control load balancing variance</help>
+ <valueHelp>
+ <format>u32:1-128</format>
+ <description>Metric variance multiplier</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-128"/>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/rip/rip-access-list.xml.i b/interface-definitions/include/rip/access-list.xml.i
index 00ee9b736..8799aa9c3 100644
--- a/interface-definitions/include/rip/rip-access-list.xml.i
+++ b/interface-definitions/include/rip/access-list.xml.i
@@ -1,4 +1,4 @@
-<!-- include start from rip/rip-access-list.xml.i -->
+<!-- include start from rip/access-list.xml.i -->
<node name="access-list">
<properties>
<help>Access-list</help>
diff --git a/interface-definitions/include/rip/rip-access-list6.xml.i b/interface-definitions/include/rip/access-list6.xml.i
index 9e4298bc0..732135253 100644
--- a/interface-definitions/include/rip/rip-access-list6.xml.i
+++ b/interface-definitions/include/rip/access-list6.xml.i
@@ -1,4 +1,4 @@
-<!-- include start from rip/rip-access-list.xml.i -->
+<!-- include start from rip/access-list.xml.i -->
<node name="access-list">
<properties>
<help>Access-list</help>
diff --git a/interface-definitions/include/rip/rip-default-information.xml.i b/interface-definitions/include/rip/default-information.xml.i
index 28c540c26..957fb3a8d 100644
--- a/interface-definitions/include/rip/rip-default-information.xml.i
+++ b/interface-definitions/include/rip/default-information.xml.i
@@ -1,4 +1,4 @@
-<!-- include start from rip/rip-default-information.xml.i -->
+<!-- include start from rip/default-information.xml.i -->
<node name="default-information">
<properties>
<help>Control distribution of default route</help>
diff --git a/interface-definitions/include/rip/rip-default-metric.xml.i b/interface-definitions/include/rip/default-metric.xml.i
index 297af5af8..c0f1f9b61 100644
--- a/interface-definitions/include/rip/rip-default-metric.xml.i
+++ b/interface-definitions/include/rip/default-metric.xml.i
@@ -1,4 +1,4 @@
-<!-- include start from rip/rip-default-metric.xml.i -->
+<!-- include start from rip/default-metric.xml.i -->
<leafNode name="default-metric">
<properties>
<help>Metric of redistributed routes</help>
diff --git a/interface-definitions/include/rip/rip-interface.xml.i b/interface-definitions/include/rip/interface.xml.i
index dd3bddd4f..baeceac1c 100644
--- a/interface-definitions/include/rip/rip-interface.xml.i
+++ b/interface-definitions/include/rip/interface.xml.i
@@ -1,4 +1,4 @@
-<!-- include start from rip/rip-interface.xml.i -->
+<!-- include start from rip/interface.xml.i -->
<tagNode name="interface">
<properties>
<help>Interface name</help>
diff --git a/interface-definitions/include/rip/rip-prefix-list.xml.i b/interface-definitions/include/rip/prefix-list.xml.i
index 2569a2a09..8e806aa35 100644
--- a/interface-definitions/include/rip/rip-prefix-list.xml.i
+++ b/interface-definitions/include/rip/prefix-list.xml.i
@@ -1,4 +1,4 @@
-<!-- include start from rip/rip-prefix-list.xml.i -->
+<!-- include start from rip/prefix-list.xml.i -->
<node name="prefix-list">
<properties>
<help>Prefix-list</help>
diff --git a/interface-definitions/include/rip/rip-prefix-list6.xml.i b/interface-definitions/include/rip/prefix-list6.xml.i
index fcf1499e0..84b6846fe 100644
--- a/interface-definitions/include/rip/rip-prefix-list6.xml.i
+++ b/interface-definitions/include/rip/prefix-list6.xml.i
@@ -1,4 +1,4 @@
-<!-- include start from rip/rip-prefix-list.xml.i -->
+<!-- include start from rip/prefix-list.xml.i -->
<node name="prefix-list">
<properties>
<help>Prefix-list</help>
diff --git a/interface-definitions/include/rip/rip-redistribute.xml.i b/interface-definitions/include/rip/redistribute.xml.i
index d7a79b007..34154a526 100644
--- a/interface-definitions/include/rip/rip-redistribute.xml.i
+++ b/interface-definitions/include/rip/redistribute.xml.i
@@ -1,4 +1,4 @@
-<!-- include start from rip/rip-redistribute.xml.i -->
+<!-- include start from rip/redistribute.xml.i -->
<leafNode name="metric">
<properties>
<help>Metric for redistributed routes</help>
diff --git a/interface-definitions/include/rip/rip-timers.xml.i b/interface-definitions/include/rip/timers.xml.i
index 129d9ed23..771a6700e 100644
--- a/interface-definitions/include/rip/rip-timers.xml.i
+++ b/interface-definitions/include/rip/timers.xml.i
@@ -1,4 +1,4 @@
-<!-- include start from rip/rip-timers.xml.i -->
+<!-- include start from rip/timers.xml.i -->
<node name="timers">
<properties>
<help>RIPng timer values</help>
diff --git a/interface-definitions/include/rip/version.xml.i b/interface-definitions/include/rip/version.xml.i
new file mode 100644
index 000000000..a35350aee
--- /dev/null
+++ b/interface-definitions/include/rip/version.xml.i
@@ -0,0 +1,18 @@
+<!-- include start from rip/version.xml.i -->
+<leafNode name="version">
+ <properties>
+ <help>Limit RIP protocol version</help>
+ <valueHelp>
+ <format>1</format>
+ <description>Allow RIPv1 only</description>
+ </valueHelp>
+ <valueHelp>
+ <format>2</format>
+ <description>Allow RIPv2 only</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-2"/>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/version/policy-version.xml.i b/interface-definitions/include/version/policy-version.xml.i
index 6d0c80518..426173a19 100644
--- a/interface-definitions/include/version/policy-version.xml.i
+++ b/interface-definitions/include/version/policy-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/policy-version.xml.i -->
-<syntaxVersion component='policy' version='2'></syntaxVersion>
+<syntaxVersion component='policy' version='3'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/pki.xml.in b/interface-definitions/pki.xml.in
index 6d137c2ce..c4fde2c78 100644
--- a/interface-definitions/pki.xml.in
+++ b/interface-definitions/pki.xml.in
@@ -3,6 +3,7 @@
<node name="pki" owner="${vyos_conf_scripts_dir}/pki.py">
<properties>
<help>VyOS PKI configuration</help>
+ <priority>300</priority>
</properties>
<children>
<tagNode name="ca">
diff --git a/interface-definitions/policy.xml.in b/interface-definitions/policy.xml.in
index 50b7cbc84..83ae714b4 100644
--- a/interface-definitions/policy.xml.in
+++ b/interface-definitions/policy.xml.in
@@ -637,6 +637,18 @@
</completionHelp>
</properties>
</leafNode>
+ <leafNode name="prefix-len">
+ <properties>
+ <help>IP prefix-length to match</help>
+ <valueHelp>
+ <format>u32:0-32</format>
+ <description>Prefix length</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-32"/>
+ </constraint>
+ </properties>
+ </leafNode>
</children>
</node>
<!-- T3304 but it overwrite node nexthop
@@ -655,12 +667,20 @@
<node name="nexthop">
<properties>
<help>IP next-hop of route to match</help>
- <valueHelp>
- <format>ipv4</format>
- <description>Next-hop IPv4 router address</description>
- </valueHelp>
</properties>
<children>
+ <leafNode name="address">
+ <properties>
+ <help>IP address to match</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>Nexthop IP address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
<leafNode name="access-list">
<properties>
<help>IP access-list to match</help>
@@ -682,6 +702,18 @@
</valueHelp>
</properties>
</leafNode>
+ <leafNode name="prefix-len">
+ <properties>
+ <help>IP prefix-length to match</help>
+ <valueHelp>
+ <format>u32:0-32</format>
+ <description>Prefix length</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-32"/>
+ </constraint>
+ </properties>
+ </leafNode>
<leafNode name="prefix-list">
<properties>
<help>IP prefix-list to match</help>
@@ -690,11 +722,26 @@
</completionHelp>
</properties>
</leafNode>
+ <leafNode name="type">
+ <properties>
+ <help>Match type</help>
+ <completionHelp>
+ <list>blackhole</list>
+ </completionHelp>
+ <valueHelp>
+ <format>blackhole</format>
+ <description>Blackhole</description>
+ </valueHelp>
+ <constraint>
+ <regex>(blackhole)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
</children>
</node>
<node name="route-source">
<properties>
- <help>test</help>
+ <help>Match advertising source address of route</help>
</properties>
<children>
<leafNode name="access-list">
@@ -760,8 +807,21 @@
</completionHelp>
</properties>
</leafNode>
+ <leafNode name="prefix-len">
+ <properties>
+ <help>IPv6 prefix-length to match</help>
+ <valueHelp>
+ <format>u32:0-128</format>
+ <description>Prefix length</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-128"/>
+ </constraint>
+ </properties>
+ </leafNode>
</children>
</node>
+ <!-- T3976 but it overwrite node nexthop
<leafNode name="nexthop">
<properties>
<help>IPv6 next-hop of route to match</help>
@@ -775,6 +835,62 @@
</properties>
</leafNode>
</children>
+ </node> -->
+ <node name="nexthop">
+ <properties>
+ <help>IPv6 next-hop of route to match</help>
+ </properties>
+ <children>
+ <leafNode name="address">
+ <properties>
+ <help>IPv6 address of next-hop</help>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>Nexthop IPv6 address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="access-list">
+ <properties>
+ <help>IPv6 access-list to match</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>IPV6 access list name</description>
+ </valueHelp>
+ <completionHelp>
+ <path>policy access-list6</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="prefix-list">
+ <properties>
+ <help>IPv6 prefix-list to match</help>
+ <completionHelp>
+ <path>policy prefix-list6</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="type">
+ <properties>
+ <help>Match type</help>
+ <completionHelp>
+ <list>blackhole</list>
+ </completionHelp>
+ <valueHelp>
+ <format>blackhole</format>
+ <description>Blackhole</description>
+ </valueHelp>
+ <constraint>
+ <regex>(blackhole)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
</node>
<node name="large-community">
<properties>
diff --git a/interface-definitions/protocols-eigrp.xml.in b/interface-definitions/protocols-eigrp.xml.in
new file mode 100644
index 000000000..88a881a1e
--- /dev/null
+++ b/interface-definitions/protocols-eigrp.xml.in
@@ -0,0 +1,17 @@
+<?xml version="1.0"?>
+<!-- Enhanced Interior Gateway Routing Protocol (EIGRP) configuration -->
+<interfaceDefinition>
+ <node name="protocols">
+ <children>
+ <node name="eigrp" owner="${vyos_conf_scripts_dir}/protocols_eigrp.py">
+ <properties>
+ <help>Enhanced Interior Gateway Routing Protocol (EIGRP)</help>
+ <priority>820</priority>
+ </properties>
+ <children>
+ #include <include/eigrp/protocol-common-config.xml.i>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/interface-definitions/protocols-rip.xml.in b/interface-definitions/protocols-rip.xml.in
index ed2153ff2..2195b0316 100644
--- a/interface-definitions/protocols-rip.xml.in
+++ b/interface-definitions/protocols-rip.xml.in
@@ -20,14 +20,14 @@
</constraint>
</properties>
</leafNode>
- #include <include/rip/rip-default-information.xml.i>
- #include <include/rip/rip-default-metric.xml.i>
+ #include <include/rip/default-information.xml.i>
+ #include <include/rip/default-metric.xml.i>
<node name="distribute-list">
<properties>
<help>Filter networks in routing updates</help>
</properties>
<children>
- #include <include/rip/rip-access-list.xml.i>
+ #include <include/rip/access-list.xml.i>
<tagNode name="interface">
<properties>
<help>Apply filtering to an interface</help>
@@ -43,14 +43,14 @@
</constraint>
</properties>
<children>
- #include <include/rip/rip-access-list.xml.i>
- #include <include/rip/rip-prefix-list.xml.i>
+ #include <include/rip/access-list.xml.i>
+ #include <include/rip/prefix-list.xml.i>
</children>
</tagNode>
- #include <include/rip/rip-prefix-list.xml.i>
+ #include <include/rip/prefix-list.xml.i>
</children>
</node>
- #include <include/rip/rip-interface.xml.i>
+ #include <include/rip/interface.xml.i>
<tagNode name="interface">
<children>
<node name="authentication">
@@ -98,6 +98,22 @@
<constraintErrorMessage>Password must be 16 characters or less</constraintErrorMessage>
</properties>
</leafNode>
+ </children>
+ </node>
+ <node name="receive">
+ <properties>
+ <help>Advertisement reception</help>
+ </properties>
+ <children>
+ #include <include/rip/version.xml.i>
+ </children>
+ </node>
+ <node name="send">
+ <properties>
+ <help>Advertisement transmission</help>
+ </properties>
+ <children>
+ #include <include/rip/version.xml.i>
</children>
</node>
</children>
@@ -166,7 +182,7 @@
<help>Redistribute BGP routes</help>
</properties>
<children>
- #include <include/rip/rip-redistribute.xml.i>
+ #include <include/rip/redistribute.xml.i>
</children>
</node>
<node name="connected">
@@ -174,7 +190,7 @@
<help>Redistribute connected routes</help>
</properties>
<children>
- #include <include/rip/rip-redistribute.xml.i>
+ #include <include/rip/redistribute.xml.i>
</children>
</node>
<node name="isis">
@@ -182,7 +198,7 @@
<help>Redistribute IS-IS routes</help>
</properties>
<children>
- #include <include/rip/rip-redistribute.xml.i>
+ #include <include/rip/redistribute.xml.i>
</children>
</node>
<node name="kernel">
@@ -190,7 +206,7 @@
<help>Redistribute kernel routes</help>
</properties>
<children>
- #include <include/rip/rip-redistribute.xml.i>
+ #include <include/rip/redistribute.xml.i>
</children>
</node>
<node name="ospf">
@@ -198,7 +214,7 @@
<help>Redistribute OSPF routes</help>
</properties>
<children>
- #include <include/rip/rip-redistribute.xml.i>
+ #include <include/rip/redistribute.xml.i>
</children>
</node>
<node name="static">
@@ -206,7 +222,7 @@
<help>Redistribute static routes</help>
</properties>
<children>
- #include <include/rip/rip-redistribute.xml.i>
+ #include <include/rip/redistribute.xml.i>
</children>
</node>
</children>
@@ -224,25 +240,9 @@
<multi/>
</properties>
</leafNode>
- #include <include/rip/rip-timers.xml.i>
+ #include <include/rip/timers.xml.i>
#include <include/route-map.xml.i>
- <leafNode name="version">
- <properties>
- <help>RIP protocol version</help>
- <valueHelp>
- <format>u32:1</format>
- <description>RIPv1</description>
- </valueHelp>
- <valueHelp>
- <format>u32:2</format>
- <description>RIPv2</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-2"/>
- </constraint>
- </properties>
- <defaultValue>2</defaultValue>
- </leafNode>
+ #include <include/rip/version.xml.i>
</children>
</node>
</children>
diff --git a/interface-definitions/protocols-ripng.xml.in b/interface-definitions/protocols-ripng.xml.in
index fe7411e65..d7e4b2514 100644
--- a/interface-definitions/protocols-ripng.xml.in
+++ b/interface-definitions/protocols-ripng.xml.in
@@ -21,14 +21,14 @@
<multi/>
</properties>
</leafNode>
- #include <include/rip/rip-default-information.xml.i>
- #include <include/rip/rip-default-metric.xml.i>
+ #include <include/rip/default-information.xml.i>
+ #include <include/rip/default-metric.xml.i>
<node name="distribute-list">
<properties>
<help>Filter networks in routing updates</help>
</properties>
<children>
- #include <include/rip/rip-access-list6.xml.i>
+ #include <include/rip/access-list6.xml.i>
<tagNode name="interface">
<properties>
<help>Apply filtering to an interface</help>
@@ -44,14 +44,14 @@
</constraint>
</properties>
<children>
- #include <include/rip/rip-access-list6.xml.i>
- #include <include/rip/rip-prefix-list6.xml.i>
+ #include <include/rip/access-list6.xml.i>
+ #include <include/rip/prefix-list6.xml.i>
</children>
</tagNode>
- #include <include/rip/rip-prefix-list6.xml.i>
+ #include <include/rip/prefix-list6.xml.i>
</children>
</node>
- #include <include/rip/rip-interface.xml.i>
+ #include <include/rip/interface.xml.i>
<leafNode name="network">
<properties>
<help>RIPng network</help>
@@ -88,7 +88,7 @@
<help>Redistribute BGP routes</help>
</properties>
<children>
- #include <include/rip/rip-redistribute.xml.i>
+ #include <include/rip/redistribute.xml.i>
</children>
</node>
<node name="connected">
@@ -96,7 +96,7 @@
<help>Redistribute connected routes</help>
</properties>
<children>
- #include <include/rip/rip-redistribute.xml.i>
+ #include <include/rip/redistribute.xml.i>
</children>
</node>
<node name="kernel">
@@ -104,7 +104,7 @@
<help>Redistribute kernel routes</help>
</properties>
<children>
- #include <include/rip/rip-redistribute.xml.i>
+ #include <include/rip/redistribute.xml.i>
</children>
</node>
<node name="ospfv3">
@@ -112,7 +112,7 @@
<help>Redistribute OSPFv3 routes</help>
</properties>
<children>
- #include <include/rip/rip-redistribute.xml.i>
+ #include <include/rip/redistribute.xml.i>
</children>
</node>
<node name="static">
@@ -120,7 +120,7 @@
<help>Redistribute static routes</help>
</properties>
<children>
- #include <include/rip/rip-redistribute.xml.i>
+ #include <include/rip/redistribute.xml.i>
</children>
</node>
</children>
@@ -139,7 +139,7 @@
</properties>
</leafNode>
#include <include/route-map.xml.i>
- #include <include/rip/rip-timers.xml.i>
+ #include <include/rip/timers.xml.i>
</children>
</node>
</children>
diff --git a/interface-definitions/protocols-static.xml.in b/interface-definitions/protocols-static.xml.in
index 3cc28e296..e89433022 100644
--- a/interface-definitions/protocols-static.xml.in
+++ b/interface-definitions/protocols-static.xml.in
@@ -7,7 +7,7 @@
<children>
<node name="static" owner="${vyos_conf_scripts_dir}/protocols_static.py">
<properties>
- <help>Static route parameters</help>
+ <help>Static Routing</help>
<priority>480</priority>
</properties>
<children>
diff --git a/interface-definitions/service_sla.xml.in b/interface-definitions/service_sla.xml.in
new file mode 100644
index 000000000..0c4f8a591
--- /dev/null
+++ b/interface-definitions/service_sla.xml.in
@@ -0,0 +1,36 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="service">
+ <children>
+ <node name="sla" owner="${vyos_conf_scripts_dir}/service_sla.py">
+ <properties>
+ <help>Service level agreement (SLA)</help>
+ </properties>
+ <children>
+ <node name="owamp-server">
+ <properties>
+ <help>One-way active measurement protocol (OWAMP) server</help>
+ </properties>
+ <children>
+ #include <include/port-number.xml.i>
+ <leafNode name="port">
+ <defaultValue>861</defaultValue>
+ </leafNode>
+ </children>
+ </node>
+ <node name="twamp-server">
+ <properties>
+ <help>Two-way active measurement protocol (TWAMP) server</help>
+ </properties>
+ <children>
+ #include <include/port-number.xml.i>
+ <leafNode name="port">
+ <defaultValue>862</defaultValue>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/interface-definitions/vrf.xml.in b/interface-definitions/vrf.xml.in
index 25a573887..3604b41c8 100644
--- a/interface-definitions/vrf.xml.in
+++ b/interface-definitions/vrf.xml.in
@@ -58,6 +58,15 @@
#include <include/bgp/protocol-common-config.xml.i>
</children>
</node>
+ <node name="eigrp" owner="${vyos_conf_scripts_dir}/protocols_eigrp.py $VAR(../../@)">
+ <properties>
+ <help>Enhanced Interior Gateway Routing Protocol (EIGRP)</help>
+ <priority>821</priority>
+ </properties>
+ <children>
+ #include <include/eigrp/protocol-common-config.xml.i>
+ </children>
+ </node>
<node name="isis" owner="${vyos_conf_scripts_dir}/protocols_isis.py $VAR(../../@)">
<properties>
<help>Intermediate System to Intermediate System (IS-IS)</help>
@@ -87,7 +96,7 @@
</node>
<node name="static" owner="${vyos_conf_scripts_dir}/protocols_static.py $VAR(../../@)">
<properties>
- <help>Static route parameters</help>
+ <help>Static Routing</help>
<priority>481</priority>
</properties>
<children>
diff --git a/op-mode-definitions/force-wamp.xml.in b/op-mode-definitions/force-wamp.xml.in
new file mode 100644
index 000000000..dbb205c6b
--- /dev/null
+++ b/op-mode-definitions/force-wamp.xml.in
@@ -0,0 +1,25 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="force">
+ <children>
+ <tagNode name="owping">
+ <properties>
+ <help>IP address of the remote OWAMP server</help>
+ <completionHelp>
+ <list>&lt;x.x.x.x&gt; &lt;h:h:h:h:h:h:h:h&gt;</list>
+ </completionHelp>
+ </properties>
+ <command>owping $3</command>
+ </tagNode>
+ <tagNode name="twping">
+ <properties>
+ <help>IP address of the remote TWAMP server</help>
+ <completionHelp>
+ <list>&lt;x.x.x.x&gt; &lt;h:h:h:h:h:h:h:h&gt;</list>
+ </completionHelp>
+ </properties>
+ <command>twping $3</command>
+ </tagNode>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/ipv6-route.xml.in b/op-mode-definitions/ipv6-route.xml.in
index 5f20444d4..4f8792f9f 100644
--- a/op-mode-definitions/ipv6-route.xml.in
+++ b/op-mode-definitions/ipv6-route.xml.in
@@ -7,7 +7,7 @@
<children>
<node name="ipv6">
<properties>
- <help>Show IPv6 routing information</help>
+ <help>Show IPv6 networking information</help>
</properties>
<children>
<leafNode name="groups">
@@ -16,14 +16,32 @@
</properties>
<command>netstat -gn6</command>
</leafNode>
-
- <leafNode name="neighbors">
+ <node name="neighbors">
<properties>
- <help>Show IPv6 Neighbor Discovery (ND) information</help>
+ <help>Show IPv6 neighbor (NDP) table</help>
</properties>
<command>${vyos_op_scripts_dir}/show_neigh.py --family inet6</command>
- </leafNode>
-
+ <children>
+ <tagNode name="interface">
+ <properties>
+ <help>Show IPv6 neighbor table for specified interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces.py -b</script>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/show_neigh.py --family inet6 --interface "$5"</command>
+ </tagNode>
+ <tagNode name="state">
+ <properties>
+ <help>Show IPv6 neighbors with specified state</help>
+ <completionHelp>
+ <list>reachable stale failed permanent</list>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/show_neigh.py --family inet6 --state "$5"</command>
+ </tagNode>
+ </children>
+ </node>
</children>
</node>
</children>
diff --git a/op-mode-definitions/pki.xml.in b/op-mode-definitions/pki.xml.in
index a1c55dcf4..346febec0 100644
--- a/op-mode-definitions/pki.xml.in
+++ b/op-mode-definitions/pki.xml.in
@@ -349,6 +349,141 @@
</node>
</children>
</node>
+ <node name="import">
+ <properties>
+ <help>Import an object</help>
+ </properties>
+ <children>
+ <node name="pki">
+ <properties>
+ <help>Import file into PKI configuration</help>
+ </properties>
+ <children>
+ <tagNode name="ca">
+ <properties>
+ <help>Import CA certificate into PKI</help>
+ <completionHelp>
+ <list>&lt;name&gt;</list>
+ </completionHelp>
+ </properties>
+ <children>
+ <tagNode name="file">
+ <properties>
+ <help>Path to CA certificate file</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/pki.py --action import --ca "$4" --filename "$6"</command>
+ </tagNode>
+ <tagNode name="key-file">
+ <properties>
+ <help>Path to private key file</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/pki.py --action import --ca "$4" --key-filename "$6"</command>
+ </tagNode>
+ </children>
+ </tagNode>
+ <tagNode name="certificate">
+ <properties>
+ <help>Import certificate into PKI</help>
+ <completionHelp>
+ <list>&lt;name&gt;</list>
+ </completionHelp>
+ </properties>
+ <children>
+ <tagNode name="file">
+ <properties>
+ <help>Path to certificate file</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/pki.py --action import --certificate "$4" --filename "$6"</command>
+ </tagNode>
+ <tagNode name="key-file">
+ <properties>
+ <help>Path to private key file</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/pki.py --action import --certificate "$4" --key-filename "$6"</command>
+ </tagNode>
+ </children>
+ </tagNode>
+ <tagNode name="crl">
+ <properties>
+ <help>Import certificate revocation list into PKI</help>
+ <completionHelp>
+ <list>&lt;CA name&gt;</list>
+ </completionHelp>
+ </properties>
+ <children>
+ <tagNode name="file">
+ <properties>
+ <help>Path to CRL file</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/pki.py --action import --crl "$4" --filename "$6"</command>
+ </tagNode>
+ </children>
+ </tagNode>
+ <tagNode name="dh">
+ <properties>
+ <help>Import DH parameters into PKI</help>
+ <completionHelp>
+ <list>&lt;name&gt;</list>
+ </completionHelp>
+ </properties>
+ <children>
+ <tagNode name="file">
+ <properties>
+ <help>Path to DH parameters file</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/pki.py --action import --dh "$4" --filename "$6"</command>
+ </tagNode>
+ </children>
+ </tagNode>
+ <tagNode name="key-pair">
+ <properties>
+ <help>Import key pair into PKI</help>
+ <completionHelp>
+ <list>&lt;name&gt;</list>
+ </completionHelp>
+ </properties>
+ <children>
+ <tagNode name="public-file">
+ <properties>
+ <help>Path to public key file</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/pki.py --action import --keypair "$4" --filename "$6"</command>
+ </tagNode>
+ <tagNode name="private-file">
+ <properties>
+ <help>Path to private key file</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/pki.py --action import --keypair "$4" --key-filename "$6"</command>
+ </tagNode>
+ </children>
+ </tagNode>
+ <node name="openvpn">
+ <properties>
+ <help>Import OpenVPN keys into PKI</help>
+ </properties>
+ <children>
+ <tagNode name="shared-secret">
+ <properties>
+ <help>Import OpenVPN shared secret key into PKI</help>
+ <completionHelp>
+ <list>&lt;name&gt;</list>
+ </completionHelp>
+ </properties>
+ <children>
+ <tagNode name="file">
+ <properties>
+ <help>Path to shared secret key file</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/pki.py --action import --openvpn "$5" --filename "$7"</command>
+ </tagNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
<node name="show">
<children>
<node name="pki">
diff --git a/op-mode-definitions/show-arp.xml.in b/op-mode-definitions/show-arp.xml.in
index 12e7d3aa2..58cc6e45e 100644
--- a/op-mode-definitions/show-arp.xml.in
+++ b/op-mode-definitions/show-arp.xml.in
@@ -6,7 +6,7 @@
<properties>
<help>Show Address Resolution Protocol (ARP) information</help>
</properties>
- <command>/usr/sbin/arp -e -n</command>
+ <command>${vyos_op_scripts_dir}/show_neigh.py --family inet</command>
<children>
<tagNode name="interface">
<properties>
@@ -15,7 +15,7 @@
<script>${vyos_completion_dir}/list_interfaces.py -b</script>
</completionHelp>
</properties>
- <command>/usr/sbin/arp -e -n -i "$4"</command>
+ <command>${vyos_op_scripts_dir}/show_neigh.py --family inet --interface "$4"</command>
</tagNode>
</children>
</node>
diff --git a/op-mode-definitions/show-ip.xml.in b/op-mode-definitions/show-ip.xml.in
index 91564440d..d342ac192 100644
--- a/op-mode-definitions/show-ip.xml.in
+++ b/op-mode-definitions/show-ip.xml.in
@@ -4,14 +4,34 @@
<children>
<node name="ip">
<properties>
- <help>Show IPv4 routing information</help>
+ <help>Show IPv4 networking information</help>
</properties>
<children>
<node name="neighbors">
<properties>
- <help>Show IPv4 Neighbor Discovery (ND) information</help>
+ <help>Show IPv4 neighbor (ARP) table</help>
</properties>
<command>${vyos_op_scripts_dir}/show_neigh.py --family inet</command>
+ <children>
+ <tagNode name="interface">
+ <properties>
+ <help>Show IPv4 neighbor table for specified interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces.py -b</script>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/show_neigh.py --family inet --interface "$5"</command>
+ </tagNode>
+ <tagNode name="state">
+ <properties>
+ <help>Show IPv4 neighbors with specified state</help>
+ <completionHelp>
+ <list>reachable stale failed permanent</list>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/show_neigh.py --family inet --state "$5"</command>
+ </tagNode>
+ </children>
</node>
</children>
</node>
diff --git a/op-mode-definitions/show-ipv6.xml.in b/op-mode-definitions/show-ipv6.xml.in
index a59c8df0c..66bc2485a 100644
--- a/op-mode-definitions/show-ipv6.xml.in
+++ b/op-mode-definitions/show-ipv6.xml.in
@@ -4,7 +4,7 @@
<children>
<node name="ipv6">
<properties>
- <help>Show IPv6 routing information</help>
+ <help>Show IPv6 networking information</help>
</properties>
<children>
<node name="access-list">
diff --git a/python/vyos/frr.py b/python/vyos/frr.py
index cbba19ab7..0ffd5cba9 100644
--- a/python/vyos/frr.py
+++ b/python/vyos/frr.py
@@ -85,7 +85,7 @@ LOG.addHandler(ch2)
_frr_daemons = ['zebra', 'bgpd', 'fabricd', 'isisd', 'ospf6d', 'ospfd', 'pbrd',
'pimd', 'ripd', 'ripngd', 'sharpd', 'staticd', 'vrrpd', 'ldpd',
- 'bfdd']
+ 'bfdd', 'eigrpd']
path_vtysh = '/usr/bin/vtysh'
path_frr_reload = '/usr/lib/frr/frr-reload.py'
diff --git a/python/vyos/pki.py b/python/vyos/pki.py
index 0b916eaae..fd91fc9bf 100644
--- a/python/vyos/pki.py
+++ b/python/vyos/pki.py
@@ -247,7 +247,7 @@ def load_private_key(raw_data, passphrase=None, wrap_tags=True):
if wrap_tags:
raw_data = wrap_private_key(raw_data, passphrase)
- if passphrase:
+ if passphrase is not None:
passphrase = bytes(passphrase, 'utf-8')
try:
diff --git a/python/vyos/util.py b/python/vyos/util.py
index de55e108b..0d62fbfe9 100644
--- a/python/vyos/util.py
+++ b/python/vyos/util.py
@@ -757,21 +757,26 @@ def dict_search_args(dict_object, *path):
dict_object = dict_object[item]
return dict_object
-def dict_search_recursive(dict_object, key):
+def dict_search_recursive(dict_object, key, path=[]):
""" Traverse a dictionary recurisvely and return the value of the key
we are looking for.
Thankfully copied from https://stackoverflow.com/a/19871956
+
+ Modified to yield optional path to found keys
"""
if isinstance(dict_object, list):
for i in dict_object:
- for x in dict_search_recursive(i, key):
- yield x
+ new_path = path + [i]
+ for x in dict_search_recursive(i, key, new_path):
+ yield x
elif isinstance(dict_object, dict):
if key in dict_object:
- yield dict_object[key]
- for j in dict_object.values():
- for x in dict_search_recursive(j, key):
+ new_path = path + [key]
+ yield dict_object[key], new_path
+ for k, j in dict_object.items():
+ new_path = path + [k]
+ for x in dict_search_recursive(j, key, new_path):
yield x
def get_bridge_fdb(interface):
diff --git a/smoketest/configs/bgp-small-internet-exchange b/smoketest/configs/bgp-small-internet-exchange
index d51f87c4a..c9da8fa77 100644
--- a/smoketest/configs/bgp-small-internet-exchange
+++ b/smoketest/configs/bgp-small-internet-exchange
@@ -269,6 +269,14 @@ policy {
}
}
}
+ rule 31 {
+ action deny
+ match {
+ ipv6 {
+ nexthop 2001:db8::1
+ }
+ }
+ }
rule 40 {
action permit
set {
diff --git a/smoketest/scripts/cli/test_load_balancning_wan.py b/smoketest/scripts/cli/test_load_balancning_wan.py
new file mode 100755
index 000000000..303dece86
--- /dev/null
+++ b/smoketest/scripts/cli/test_load_balancning_wan.py
@@ -0,0 +1,257 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+import os
+import unittest
+import time
+
+from base_vyostest_shim import VyOSUnitTestSHIM
+from vyos.configsession import ConfigSessionError
+from vyos.ifconfig import Section
+from vyos.util import call
+from vyos.util import cmd
+
+
+base_path = ['load-balancing']
+
+
+def create_netns(name):
+ return call(f'sudo ip netns add {name}')
+
+def create_veth_pair(local='veth0', peer='ceth0'):
+ return call(f'sudo ip link add {local} type veth peer name {peer}')
+
+def move_interface_to_netns(iface, netns_name):
+ return call(f'sudo ip link set {iface} netns {netns_name}')
+
+def rename_interface(iface, new_name):
+ return call(f'sudo ip link set {iface} name {new_name}')
+
+def cmd_in_netns(netns, cmd):
+ return call(f'sudo ip netns exec {netns} {cmd}')
+
+def delete_netns(name):
+ return call(f'sudo ip netns del {name}')
+
+
+class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ super(TestLoadBalancingWan, cls).setUpClass()
+
+ # ensure we can also run this test on a live system - so lets clean
+ # out the current configuration :)
+ cls.cli_delete(cls, base_path)
+
+ def tearDown(self):
+ self.cli_delete(base_path)
+ self.cli_commit()
+
+ def test_table_routes(self):
+
+ ns1 = 'ns201'
+ ns2 = 'ns202'
+ ns3 = 'ns203'
+ iface1 = 'eth201'
+ iface2 = 'eth202'
+ iface3 = 'eth203'
+ container_iface1 = 'ceth0'
+ container_iface2 = 'ceth1'
+ container_iface3 = 'ceth2'
+
+ # Create network namespeces
+ create_netns(ns1)
+ create_netns(ns2)
+ create_netns(ns3)
+ create_veth_pair(iface1, container_iface1)
+ create_veth_pair(iface2, container_iface2)
+ create_veth_pair(iface3, container_iface3)
+ move_interface_to_netns(container_iface1, ns1)
+ move_interface_to_netns(container_iface2, ns2)
+ move_interface_to_netns(container_iface3, ns3)
+ call(f'sudo ip address add 203.0.113.10/24 dev {iface1}')
+ call(f'sudo ip address add 192.0.2.10/24 dev {iface2}')
+ call(f'sudo ip address add 198.51.100.10/24 dev {iface3}')
+ call(f'sudo ip link set dev {iface1} up')
+ call(f'sudo ip link set dev {iface2} up')
+ call(f'sudo ip link set dev {iface3} up')
+ cmd_in_netns(ns1, f'ip link set {container_iface1} name eth0')
+ cmd_in_netns(ns2, f'ip link set {container_iface2} name eth0')
+ cmd_in_netns(ns3, f'ip link set {container_iface3} name eth0')
+ cmd_in_netns(ns1, 'ip address add 203.0.113.1/24 dev eth0')
+ cmd_in_netns(ns2, 'ip address add 192.0.2.1/24 dev eth0')
+ cmd_in_netns(ns3, 'ip address add 198.51.100.1/24 dev eth0')
+ cmd_in_netns(ns1, 'ip link set dev eth0 up')
+ cmd_in_netns(ns2, 'ip link set dev eth0 up')
+ cmd_in_netns(ns3, 'ip link set dev eth0 up')
+
+ # Set load-balancing configuration
+ self.cli_set(base_path + ['wan', 'interface-health', iface1, 'failure-count', '2'])
+ self.cli_set(base_path + ['wan', 'interface-health', iface1, 'nexthop', '203.0.113.1'])
+ self.cli_set(base_path + ['wan', 'interface-health', iface1, 'success-count', '1'])
+ self.cli_set(base_path + ['wan', 'interface-health', iface2, 'failure-count', '2'])
+ self.cli_set(base_path + ['wan', 'interface-health', iface2, 'nexthop', '192.0.2.1'])
+ self.cli_set(base_path + ['wan', 'interface-health', iface2, 'success-count', '1'])
+
+ self.cli_set(base_path + ['wan', 'rule', '10', 'inbound-interface', iface3])
+ self.cli_set(base_path + ['wan', 'rule', '10', 'source', 'address', '198.51.100.0/24'])
+
+
+ # commit changes
+ self.cli_commit()
+
+ time.sleep(5)
+ # Check default routes in tables 201, 202
+ # Expected values
+ original = 'default via 203.0.113.1 dev eth201'
+ tmp = cmd('sudo ip route show table 201')
+ self.assertEqual(tmp, original)
+
+ original = 'default via 192.0.2.1 dev eth202'
+ tmp = cmd('sudo ip route show table 202')
+ self.assertEqual(tmp, original)
+
+ # Delete veth interfaces and netns
+ for iface in [iface1, iface2]:
+ call(f'sudo ip link del dev {iface}')
+
+ delete_netns(ns1)
+ delete_netns(ns2)
+
+ def test_check_chains(self):
+
+ ns1 = 'nsA'
+ ns2 = 'nsB'
+ ns3 = 'nsC'
+ iface1 = 'veth1'
+ iface2 = 'veth2'
+ iface3 = 'veth3'
+ container_iface1 = 'ceth0'
+ container_iface2 = 'ceth1'
+ container_iface3 = 'ceth2'
+ mangle_isp1 = """table ip mangle {
+ chain ISP_veth1 {
+ counter ct mark set 0xc9
+ counter meta mark set 0xc9
+ counter accept
+ }
+}"""
+ mangle_isp2 = """table ip mangle {
+ chain ISP_veth2 {
+ counter ct mark set 0xca
+ counter meta mark set 0xca
+ counter accept
+ }
+}"""
+ mangle_prerouting = """table ip mangle {
+ chain PREROUTING {
+ type filter hook prerouting priority mangle; policy accept;
+ counter jump WANLOADBALANCE_PRE
+ }
+}"""
+ mangle_wanloadbalance_pre = """table ip mangle {
+ chain WANLOADBALANCE_PRE {
+ iifname "veth3" ip saddr 198.51.100.0/24 ct state new counter jump ISP_veth1
+ iifname "veth3" ip saddr 198.51.100.0/24 ct state new counter jump ISP_veth2
+ iifname "veth3" ip saddr 198.51.100.0/24 counter meta mark set ct mark
+ }
+}"""
+ nat_wanloadbalance = """table ip nat {
+ chain WANLOADBALANCE {
+ ct mark 0xc9 counter snat to 203.0.113.10
+ ct mark 0xca counter snat to 192.0.2.10
+ }
+}"""
+ nat_vyos_pre_snat_hook = """table ip nat {
+ chain VYOS_PRE_SNAT_HOOK {
+ counter jump WANLOADBALANCE
+ return
+ }
+}"""
+
+ # Create network namespeces
+ create_netns(ns1)
+ create_netns(ns2)
+ create_netns(ns3)
+ create_veth_pair(iface1, container_iface1)
+ create_veth_pair(iface2, container_iface2)
+ create_veth_pair(iface3, container_iface3)
+ move_interface_to_netns(container_iface1, ns1)
+ move_interface_to_netns(container_iface2, ns2)
+ move_interface_to_netns(container_iface3, ns3)
+ call(f'sudo ip address add 203.0.113.10/24 dev {iface1}')
+ call(f'sudo ip address add 192.0.2.10/24 dev {iface2}')
+ call(f'sudo ip address add 198.51.100.10/24 dev {iface3}')
+ call(f'sudo ip link set dev {iface1} up')
+ call(f'sudo ip link set dev {iface2} up')
+ call(f'sudo ip link set dev {iface3} up')
+ cmd_in_netns(ns1, f'ip link set {container_iface1} name eth0')
+ cmd_in_netns(ns2, f'ip link set {container_iface2} name eth0')
+ cmd_in_netns(ns3, f'ip link set {container_iface3} name eth0')
+ cmd_in_netns(ns1, 'ip address add 203.0.113.1/24 dev eth0')
+ cmd_in_netns(ns2, 'ip address add 192.0.2.1/24 dev eth0')
+ cmd_in_netns(ns3, 'ip address add 198.51.100.1/24 dev eth0')
+ cmd_in_netns(ns1, 'ip link set dev eth0 up')
+ cmd_in_netns(ns2, 'ip link set dev eth0 up')
+ cmd_in_netns(ns3, 'ip link set dev eth0 up')
+
+ # Set load-balancing configuration
+ self.cli_set(base_path + ['wan', 'interface-health', iface1, 'failure-count', '2'])
+ self.cli_set(base_path + ['wan', 'interface-health', iface1, 'nexthop', '203.0.113.1'])
+ self.cli_set(base_path + ['wan', 'interface-health', iface1, 'success-count', '1'])
+ self.cli_set(base_path + ['wan', 'interface-health', iface2, 'failure-count', '2'])
+ self.cli_set(base_path + ['wan', 'interface-health', iface2, 'nexthop', '192.0.2.1'])
+ self.cli_set(base_path + ['wan', 'interface-health', iface2, 'success-count', '1'])
+ self.cli_set(base_path + ['wan', 'rule', '10', 'inbound-interface', iface3])
+ self.cli_set(base_path + ['wan', 'rule', '10', 'source', 'address', '198.51.100.0/24'])
+ self.cli_set(base_path + ['wan', 'rule', '10', 'interface', iface1])
+ self.cli_set(base_path + ['wan', 'rule', '10', 'interface', iface2])
+
+ # commit changes
+ self.cli_commit()
+
+ time.sleep(5)
+
+ # Check mangle chains
+ tmp = cmd(f'sudo nft -s list chain mangle ISP_{iface1}')
+ self.assertEqual(tmp, mangle_isp1)
+
+ tmp = cmd(f'sudo nft -s list chain mangle ISP_{iface2}')
+ self.assertEqual(tmp, mangle_isp2)
+
+ tmp = cmd(f'sudo nft -s list chain mangle PREROUTING')
+ self.assertEqual(tmp, mangle_prerouting)
+
+ tmp = cmd(f'sudo nft -s list chain mangle WANLOADBALANCE_PRE')
+ self.assertEqual(tmp, mangle_wanloadbalance_pre)
+
+ # Check nat chains
+ tmp = cmd(f'sudo nft -s list chain nat WANLOADBALANCE')
+ self.assertEqual(tmp, nat_wanloadbalance)
+
+ tmp = cmd(f'sudo nft -s list chain nat VYOS_PRE_SNAT_HOOK')
+ self.assertEqual(tmp, nat_vyos_pre_snat_hook)
+
+ # Delete veth interfaces and netns
+ for iface in [iface1, iface2]:
+ call(f'sudo ip link del dev {iface}')
+
+ delete_netns(ns1)
+ delete_netns(ns2)
+
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_pki.py b/smoketest/scripts/cli/test_pki.py
index e92123dbc..cba5ffdde 100755
--- a/smoketest/scripts/cli/test_pki.py
+++ b/smoketest/scripts/cli/test_pki.py
@@ -128,6 +128,27 @@ g6a75NnEXo0J6YLAOOxd8fD2/HidhbceCmTF+3msidIzCsBidBkgn6V5TXx2IyMS
xGsJxVHfSKeooUQn6q76sg==
"""
+valid_update_cert = """
+MIICJTCCAcugAwIBAgIUZJqjNmPfVQwePjNFBtB6WI31ThMwCgYIKoZIzj0EAwIw
+VzELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNv
+bWUtQ2l0eTENMAsGA1UECgwEVnlPUzEQMA4GA1UEAwwHdnlvcy5pbzAeFw0yMjA1
+MzExNTE3NDlaFw0yMzA1MzExNTE3NDlaMFcxCzAJBgNVBAYTAkdCMRMwEQYDVQQI
+DApTb21lLVN0YXRlMRIwEAYDVQQHDAlTb21lLUNpdHkxDTALBgNVBAoMBFZ5T1Mx
+EDAOBgNVBAMMB3Z5b3MuaW8wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQMe0h/
+3CdD8mEgy+klk55QfJ8R3ZycefxCn4abWjzTXz/TuCIxqb4wpRT8DZtIn4NRimFT
+mODYdEDOYxFtZm37o3UwczAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIHgDAT
+BgNVHSUEDDAKBggrBgEFBQcDAjAdBgNVHQ4EFgQUqH7KSZpzArpMFuxLXqI8e1QD
+fBkwHwYDVR0jBBgwFoAUqH7KSZpzArpMFuxLXqI8e1QDfBkwCgYIKoZIzj0EAwID
+SAAwRQIhAKofUgRtcUljmbubPF6sqHtn/3TRvuafl8VfPbk3s2bJAiBp3Q1AnU/O
+i7t5FGhCgnv5m8DW2F3LZPCJdW4ELQ3d9A==
+"""
+
+valid_update_private_key = """
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgvyODf22w/p7Zgfz9
+dyLIT09LqLOrUN6zbAecfukiiiyhRANCAAQMe0h/3CdD8mEgy+klk55QfJ8R3Zyc
+efxCn4abWjzTXz/TuCIxqb4wpRT8DZtIn4NRimFTmODYdEDOYxFtZm37
+"""
+
class TestPKI(VyOSUnitTestSHIM.TestCase):
@classmethod
def setUpClass(cls):
@@ -189,5 +210,41 @@ class TestPKI(VyOSUnitTestSHIM.TestCase):
with self.assertRaises(ConfigSessionError):
self.cli_commit()
+ def test_certificate_in_use(self):
+ self.cli_set(base_path + ['certificate', 'smoketest', 'certificate', valid_ca_cert.replace('\n','')])
+ self.cli_set(base_path + ['certificate', 'smoketest', 'private', 'key', valid_ca_private_key.replace('\n','')])
+ self.cli_commit()
+
+ self.cli_set(['service', 'https', 'certificates', 'certificate', 'smoketest'])
+ self.cli_commit()
+
+ self.cli_delete(base_path + ['certificate', 'smoketest'])
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ self.cli_delete(['service', 'https', 'certificates', 'certificate'])
+
+ def test_certificate_https_update(self):
+ self.cli_set(base_path + ['certificate', 'smoketest', 'certificate', valid_ca_cert.replace('\n','')])
+ self.cli_set(base_path + ['certificate', 'smoketest', 'private', 'key', valid_ca_private_key.replace('\n','')])
+ self.cli_commit()
+
+ self.cli_set(['service', 'https', 'certificates', 'certificate', 'smoketest'])
+ self.cli_commit()
+
+ cert_data = None
+
+ with open('/etc/ssl/certs/smoketest.pem') as f:
+ cert_data = f.read()
+
+ self.cli_set(base_path + ['certificate', 'smoketest', 'certificate', valid_update_cert.replace('\n','')])
+ self.cli_set(base_path + ['certificate', 'smoketest', 'private', 'key', valid_update_private_key.replace('\n','')])
+ self.cli_commit()
+
+ with open('/etc/ssl/certs/smoketest.pem') as f:
+ self.assertNotEqual(cert_data, f.read())
+
+ self.cli_delete(['service', 'https', 'certificates', 'certificate'])
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_policy.py b/smoketest/scripts/cli/test_policy.py
index e8c6ff19b..f175d7df7 100755
--- a/smoketest/scripts/cli/test_policy.py
+++ b/smoketest/scripts/cli/test_policy.py
@@ -711,13 +711,20 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
large_community_list = 'bgp-large-community-123456'
prefix_list = 'foo-pfx-list'
- ipv6_nexthop = 'fe80::1'
+ ipv6_nexthop_address = 'fe80::1'
local_pref = '300'
metric = '50'
peer = '2.3.4.5'
tag = '6542'
goto = '25'
+ ipv4_nexthop_address= '192.0.2.2'
+ ipv4_prefix_len= '18'
+ ipv6_prefix_len= '122'
+ ipv4_nexthop_type= 'blackhole'
+ ipv6_nexthop_type= 'blackhole'
+
+
test_data = {
'foo-map-bar' : {
'rule' : {
@@ -785,7 +792,11 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
'30' : {
'action' : 'permit',
'match' : {
- 'ipv6-nexthop' : ipv6_nexthop,
+ 'ipv6-nexthop-address' : ipv6_nexthop_address,
+ 'ipv6-nexthop-access-list' : access_list,
+ 'ipv6-nexthop-prefix-list' : prefix_list,
+ 'ipv6-nexthop-type' : ipv6_nexthop_type,
+ 'ipv6-address-pfx-len' : ipv6_prefix_len,
'large-community' : large_community_list,
'local-pref' : local_pref,
'metric': metric,
@@ -793,6 +804,25 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
'peer' : peer,
},
},
+ '40' : {
+ 'action' : 'permit',
+ 'match' : {
+ 'ip-nexthop-addr' : ipv4_nexthop_address,
+ 'ip-address-pfx-len' : ipv4_prefix_len,
+ },
+ },
+ '42' : {
+ 'action' : 'deny',
+ 'match' : {
+ 'ip-nexthop-plen' : ipv4_prefix_len,
+ },
+ },
+ '44' : {
+ 'action' : 'permit',
+ 'match' : {
+ 'ip-nexthop-type' : ipv4_nexthop_type,
+ },
+ },
},
},
'complicated-configuration' : {
@@ -917,10 +947,18 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
self.cli_set(path + ['rule', rule, 'match', 'ip', 'address', 'access-list', rule_config['match']['ip-address-acl']])
if 'ip-address-pfx' in rule_config['match']:
self.cli_set(path + ['rule', rule, 'match', 'ip', 'address', 'prefix-list', rule_config['match']['ip-address-pfx']])
+ if 'ip-address-pfx-len' in rule_config['match']:
+ self.cli_set(path + ['rule', rule, 'match', 'ip', 'address', 'prefix-len', rule_config['match']['ip-address-pfx-len']])
if 'ip-nexthop-acl' in rule_config['match']:
self.cli_set(path + ['rule', rule, 'match', 'ip', 'nexthop', 'access-list', rule_config['match']['ip-nexthop-acl']])
if 'ip-nexthop-pfx' in rule_config['match']:
self.cli_set(path + ['rule', rule, 'match', 'ip', 'nexthop', 'prefix-list', rule_config['match']['ip-nexthop-pfx']])
+ if 'ip-nexthop-addr' in rule_config['match']:
+ self.cli_set(path + ['rule', rule, 'match', 'ip', 'nexthop', 'address', rule_config['match']['ip-nexthop-addr']])
+ if 'ip-nexthop-plen' in rule_config['match']:
+ self.cli_set(path + ['rule', rule, 'match', 'ip', 'nexthop', 'prefix-len', rule_config['match']['ip-nexthop-plen']])
+ if 'ip-nexthop-type' in rule_config['match']:
+ self.cli_set(path + ['rule', rule, 'match', 'ip', 'nexthop', 'type', rule_config['match']['ip-nexthop-type']])
if 'ip-route-source-acl' in rule_config['match']:
self.cli_set(path + ['rule', rule, 'match', 'ip', 'route-source', 'access-list', rule_config['match']['ip-route-source-acl']])
if 'ip-route-source-pfx' in rule_config['match']:
@@ -929,8 +967,16 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
self.cli_set(path + ['rule', rule, 'match', 'ipv6', 'address', 'access-list', rule_config['match']['ipv6-address-acl']])
if 'ipv6-address-pfx' in rule_config['match']:
self.cli_set(path + ['rule', rule, 'match', 'ipv6', 'address', 'prefix-list', rule_config['match']['ipv6-address-pfx']])
- if 'ipv6-nexthop' in rule_config['match']:
- self.cli_set(path + ['rule', rule, 'match', 'ipv6', 'nexthop', rule_config['match']['ipv6-nexthop']])
+ if 'ipv6-address-pfx-len' in rule_config['match']:
+ self.cli_set(path + ['rule', rule, 'match', 'ipv6', 'address', 'prefix-len', rule_config['match']['ipv6-address-pfx-len']])
+ if 'ipv6-nexthop-address' in rule_config['match']:
+ self.cli_set(path + ['rule', rule, 'match', 'ipv6', 'nexthop', 'address', rule_config['match']['ipv6-nexthop-address']])
+ if 'ipv6-nexthop-access-list' in rule_config['match']:
+ self.cli_set(path + ['rule', rule, 'match', 'ipv6', 'nexthop', 'access-list', rule_config['match']['ipv6-nexthop-access-list']])
+ if 'ipv6-nexthop-prefix-list' in rule_config['match']:
+ self.cli_set(path + ['rule', rule, 'match', 'ipv6', 'nexthop', 'prefix-list', rule_config['match']['ipv6-nexthop-prefix-list']])
+ if 'ipv6-nexthop-type' in rule_config['match']:
+ self.cli_set(path + ['rule', rule, 'match', 'ipv6', 'nexthop', 'type', rule_config['match']['ipv6-nexthop-type']])
if 'large-community' in rule_config['match']:
self.cli_set(path + ['rule', rule, 'match', 'large-community', 'large-community-list', rule_config['match']['large-community']])
if 'local-pref' in rule_config['match']:
@@ -1057,12 +1103,24 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
if 'ip-address-pfx' in rule_config['match']:
tmp = f'match ip address prefix-list {rule_config["match"]["ip-address-pfx"]}'
self.assertIn(tmp, config)
+ if 'ip-address-pfx-len' in rule_config['match']:
+ tmp = f'match ip address prefix-len {rule_config["match"]["ip-address-pfx-len"]}'
+ self.assertIn(tmp, config)
if 'ip-nexthop-acl' in rule_config['match']:
tmp = f'match ip next-hop {rule_config["match"]["ip-nexthop-acl"]}'
self.assertIn(tmp, config)
if 'ip-nexthop-pfx' in rule_config['match']:
tmp = f'match ip next-hop prefix-list {rule_config["match"]["ip-nexthop-pfx"]}'
self.assertIn(tmp, config)
+ if 'ip-nexthop-addr' in rule_config['match']:
+ tmp = f'match ip next-hop address {rule_config["match"]["ip-nexthop-addr"]}'
+ self.assertIn(tmp, config)
+ if 'ip-nexthop-plen' in rule_config['match']:
+ tmp = f'match ip next-hop prefix-len {rule_config["match"]["ip-nexthop-plen"]}'
+ self.assertIn(tmp, config)
+ if 'ip-nexthop-type' in rule_config['match']:
+ tmp = f'match ip next-hop type {rule_config["match"]["ip-nexthop-type"]}'
+ self.assertIn(tmp, config)
if 'ip-route-source-acl' in rule_config['match']:
tmp = f'match ip route-source {rule_config["match"]["ip-route-source-acl"]}'
self.assertIn(tmp, config)
@@ -1075,8 +1133,20 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
if 'ipv6-address-pfx' in rule_config['match']:
tmp = f'match ipv6 address prefix-list {rule_config["match"]["ipv6-address-pfx"]}'
self.assertIn(tmp, config)
- if 'ipv6-nexthop' in rule_config['match']:
- tmp = f'match ipv6 next-hop address {rule_config["match"]["ipv6-nexthop"]}'
+ if 'ipv6-address-pfx-len' in rule_config['match']:
+ tmp = f'match ipv6 address prefix-len {rule_config["match"]["ipv6-address-pfx-len"]}'
+ self.assertIn(tmp, config)
+ if 'ipv6-nexthop-address' in rule_config['match']:
+ tmp = f'match ipv6 next-hop address {rule_config["match"]["ipv6-nexthop-address"]}'
+ self.assertIn(tmp, config)
+ if 'ipv6-nexthop-access-list' in rule_config['match']:
+ tmp = f'match ipv6 next-hop {rule_config["match"]["ipv6-nexthop-access-list"]}'
+ self.assertIn(tmp, config)
+ if 'ipv6-nexthop-prefix-list' in rule_config['match']:
+ tmp = f'match ipv6 next-hop prefix-list {rule_config["match"]["ipv6-nexthop-prefix-list"]}'
+ self.assertIn(tmp, config)
+ if 'ipv6-nexthop-type' in rule_config['match']:
+ tmp = f'match ipv6 next-hop type {rule_config["match"]["ipv6-nexthop-type"]}'
self.assertIn(tmp, config)
if 'large-community' in rule_config['match']:
tmp = f'match large-community {rule_config["match"]["large-community"]}'
diff --git a/smoketest/scripts/cli/test_protocols_rip.py b/smoketest/scripts/cli/test_protocols_rip.py
index ee882943b..11385adb5 100755
--- a/smoketest/scripts/cli/test_protocols_rip.py
+++ b/smoketest/scripts/cli/test_protocols_rip.py
@@ -159,15 +159,24 @@ class TestProtocolsRIP(VyOSUnitTestSHIM.TestCase):
self.assertNotIn(zebra_route_map, frrconfig)
def test_rip_03_version(self):
- version = '1'
+ rx_version = '1'
+ tx_version = '2'
+ interface = 'eth0'
+
+ self.cli_set(base_path + ['version', tx_version])
+ self.cli_set(base_path + ['interface', interface, 'send', 'version', tx_version])
+ self.cli_set(base_path + ['interface', interface, 'receive', 'version', rx_version])
- self.cli_set(base_path + ['version', version])
# commit changes
self.cli_commit()
# Verify FRR configuration
frrconfig = self.getFRRconfig('router rip')
- self.assertIn(f'version {version}', frrconfig)
+ self.assertIn(f'version {tx_version}', frrconfig)
+
+ frrconfig = self.getFRRconfig(f'interface {interface}')
+ self.assertIn(f' ip rip receive version {rx_version}', frrconfig)
+ self.assertIn(f' ip rip send version {tx_version}', frrconfig)
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/src/conf_mode/pki.py b/src/conf_mode/pki.py
index efa3578b4..29ed7b1b7 100755
--- a/src/conf_mode/pki.py
+++ b/src/conf_mode/pki.py
@@ -29,12 +29,60 @@ from vyos.pki import load_private_key
from vyos.pki import load_crl
from vyos.pki import load_dh_parameters
from vyos.util import ask_input
+from vyos.util import call
+from vyos.util import dict_search_args
from vyos.util import dict_search_recursive
from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
+# keys to recursively search for under specified path, script to call if update required
+sync_search = [
+ {
+ 'keys': ['certificate'],
+ 'path': ['service', 'https'],
+ 'script': '/usr/libexec/vyos/conf_mode/https.py'
+ },
+ {
+ 'keys': ['certificate', 'ca_certificate'],
+ 'path': ['interfaces', 'ethernet'],
+ 'script': '/usr/libexec/vyos/conf_mode/interfaces-ethernet.py'
+ },
+ {
+ 'keys': ['certificate', 'ca_certificate', 'dh_params', 'shared_secret_key', 'auth_key', 'crypt_key'],
+ 'path': ['interfaces', 'openvpn'],
+ 'script': '/usr/libexec/vyos/conf_mode/interfaces-openvpn.py'
+ },
+ {
+ 'keys': ['certificate', 'ca_certificate', 'local_key', 'remote_key'],
+ 'path': ['vpn', 'ipsec'],
+ 'script': '/usr/libexec/vyos/conf_mode/vpn_ipsec.py'
+ },
+ {
+ 'keys': ['certificate', 'ca_certificate'],
+ 'path': ['vpn', 'openconnect'],
+ 'script': '/usr/libexec/vyos/conf_mode/vpn_openconnect.py'
+ },
+ {
+ 'keys': ['certificate', 'ca_certificate'],
+ 'path': ['vpn', 'sstp'],
+ 'script': '/usr/libexec/vyos/conf_mode/vpn_sstp.py'
+ }
+]
+
+# key from other config nodes -> key in pki['changed'] and pki
+sync_translate = {
+ 'certificate': 'certificate',
+ 'ca_certificate': 'ca',
+ 'dh_params': 'dh',
+ 'local_key': 'key_pair',
+ 'remote_key': 'key_pair',
+ 'shared_secret_key': 'openvpn',
+ 'auth_key': 'openvpn',
+ 'crypt_key': 'openvpn'
+}
+
def get_config(config=None):
if config:
conf = config
@@ -47,12 +95,21 @@ def get_config(config=None):
no_tag_node_value_mangle=True)
pki['changed'] = {}
- tmp = node_changed(conf, base + ['ca'], key_mangling=('-', '_'))
+ tmp = node_changed(conf, base + ['ca'], key_mangling=('-', '_'), recursive=True)
if tmp: pki['changed'].update({'ca' : tmp})
- tmp = node_changed(conf, base + ['certificate'], key_mangling=('-', '_'))
+ tmp = node_changed(conf, base + ['certificate'], key_mangling=('-', '_'), recursive=True)
if tmp: pki['changed'].update({'certificate' : tmp})
+ tmp = node_changed(conf, base + ['dh'], key_mangling=('-', '_'), recursive=True)
+ if tmp: pki['changed'].update({'dh' : tmp})
+
+ tmp = node_changed(conf, base + ['key-pair'], key_mangling=('-', '_'), recursive=True)
+ if tmp: pki['changed'].update({'key_pair' : tmp})
+
+ tmp = node_changed(conf, base + ['openvpn', 'shared-secret'], key_mangling=('-', '_'), recursive=True)
+ if tmp: pki['changed'].update({'openvpn' : tmp})
+
# We only merge on the defaults of there is a configuration at all
if conf.exists(base):
default_values = defaults(base)
@@ -164,17 +221,30 @@ def verify(pki):
if 'changed' in pki:
# if the list is getting longer, we can move to a dict() and also embed the
# search key as value from line 173 or 176
- for cert_type in ['ca', 'certificate']:
- if not cert_type in pki['changed']:
- continue
- for certificate in pki['changed'][cert_type]:
- if cert_type not in pki or certificate not in pki['changed'][cert_type]:
- if cert_type == 'ca':
- if certificate in dict_search_recursive(pki['system'], 'ca_certificate'):
- raise ConfigError(f'CA certificate "{certificate}" is still in use!')
- elif cert_type == 'certificate':
- if certificate in dict_search_recursive(pki['system'], 'certificate'):
- raise ConfigError(f'Certificate "{certificate}" is still in use!')
+ for search in sync_search:
+ for key in search['keys']:
+ changed_key = sync_translate[key]
+
+ if changed_key not in pki['changed']:
+ continue
+
+ for item_name in pki['changed'][changed_key]:
+ node_present = False
+ if changed_key == 'openvpn':
+ node_present = dict_search_args(pki, 'openvpn', 'shared_secret', item_name)
+ else:
+ node_present = dict_search_args(pki, changed_key, item_name)
+
+ if not node_present:
+ search_dict = dict_search_args(pki['system'], *search['path'])
+
+ if not search_dict:
+ continue
+
+ for found_name, found_path in dict_search_recursive(search_dict, key):
+ if found_name == item_name:
+ path_str = " ".join(search['path'] + found_path)
+ raise ConfigError(f'PKI object "{item_name}" still in use by "{path_str}"')
return None
@@ -188,7 +258,38 @@ def apply(pki):
if not pki:
return None
- # XXX: restart services if the content of a certificate changes
+ if 'changed' in pki:
+ for search in sync_search:
+ for key in search['keys']:
+ changed_key = sync_translate[key]
+
+ if changed_key not in pki['changed']:
+ continue
+
+ for item_name in pki['changed'][changed_key]:
+ node_present = False
+ if changed_key == 'openvpn':
+ node_present = dict_search_args(pki, 'openvpn', 'shared_secret', item_name)
+ else:
+ node_present = dict_search_args(pki, changed_key, item_name)
+
+ if node_present:
+ search_dict = dict_search_args(pki['system'], *search['path'])
+
+ if not search_dict:
+ continue
+
+ for found_name, found_path in dict_search_recursive(search_dict, key):
+ if found_name == item_name:
+ path_str = ' '.join(search['path'] + found_path)
+ print(f'pki: Updating config: {path_str} {found_name}')
+
+ script = search['script']
+ if found_path[0] == 'interfaces':
+ ifname = found_path[2]
+ call(f'VYOS_TAGNODE_VALUE={ifname} {script}')
+ else:
+ call(script)
return None
diff --git a/src/conf_mode/policy.py b/src/conf_mode/policy.py
index ef6008140..3008a20e0 100755
--- a/src/conf_mode/policy.py
+++ b/src/conf_mode/policy.py
@@ -150,6 +150,16 @@ def verify(policy):
tmp = dict_search('match.ipv6.address.prefix_list', rule_config)
if tmp and tmp not in policy.get('prefix_list6', []):
raise ConfigError(f'prefix-list6 {tmp} does not exist!')
+
+ # Specified access_list6 in nexthop must exist
+ tmp = dict_search('match.ipv6.nexthop.access_list', rule_config)
+ if tmp and tmp not in policy.get('access_list6', []):
+ raise ConfigError(f'access_list6 {tmp} does not exist!')
+
+ # Specified prefix-list6 in nexthop must exist
+ tmp = dict_search('match.ipv6.nexthop.prefix_list', rule_config)
+ if tmp and tmp not in policy.get('prefix_list6', []):
+ raise ConfigError(f'prefix-list6 {tmp} does not exist!')
# When routing protocols are active some use prefix-lists, route-maps etc.
# to apply the systems routing policy to the learned or redistributed routes.
diff --git a/src/conf_mode/protocols_eigrp.py b/src/conf_mode/protocols_eigrp.py
new file mode 100755
index 000000000..c1a1a45e1
--- /dev/null
+++ b/src/conf_mode/protocols_eigrp.py
@@ -0,0 +1,123 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+from sys import exit
+from sys import argv
+
+from vyos.config import Config
+from vyos.configdict import dict_merge
+from vyos.template import render_to_string
+from vyos import ConfigError
+from vyos import frr
+from vyos import airbag
+airbag.enable()
+
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+
+ vrf = None
+ if len(argv) > 1:
+ vrf = argv[1]
+
+ base_path = ['protocols', 'eigrp']
+
+ # eqivalent of the C foo ? 'a' : 'b' statement
+ base = vrf and ['vrf', 'name', vrf, 'protocols', 'eigrp'] or base_path
+ eigrp = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True, no_tag_node_value_mangle=True)
+
+ # Assign the name of our VRF context. This MUST be done before the return
+ # statement below, else on deletion we will delete the default instance
+ # instead of the VRF instance.
+ if vrf: eigrp.update({'vrf' : vrf})
+
+ if not conf.exists(base):
+ eigrp.update({'deleted' : ''})
+ if not vrf:
+ # We are running in the default VRF context, thus we can not delete
+ # our main EIGRP instance if there are dependent EIGRP VRF instances.
+ eigrp['dependent_vrfs'] = conf.get_config_dict(['vrf', 'name'],
+ key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+ return eigrp
+
+ # We also need some additional information from the config, prefix-lists
+ # and route-maps for instance. They will be used in verify().
+ #
+ # XXX: one MUST always call this without the key_mangling() option! See
+ # vyos.configverify.verify_common_route_maps() for more information.
+ tmp = conf.get_config_dict(['policy'])
+ # Merge policy dict into "regular" config dict
+ eigrp = dict_merge(tmp, eigrp)
+
+ import pprint
+ pprint.pprint(eigrp)
+ return eigrp
+
+def verify(eigrp):
+ pass
+
+def generate(eigrp):
+ if not eigrp or 'deleted' in eigrp:
+ return None
+
+ eigrp['protocol'] = 'eigrp' # required for frr/vrf.route-map.frr.j2
+ eigrp['frr_zebra_config'] = render_to_string('frr/vrf.route-map.frr.j2', eigrp)
+ eigrp['frr_eigrpd_config'] = render_to_string('frr/eigrpd.frr.j2', eigrp)
+
+def apply(eigrp):
+ eigrp_daemon = 'eigrpd'
+ zebra_daemon = 'zebra'
+
+ # Save original configuration prior to starting any commit actions
+ frr_cfg = frr.FRRConfig()
+
+ # The route-map used for the FIB (zebra) is part of the zebra daemon
+ frr_cfg.load_configuration(zebra_daemon)
+ frr_cfg.modify_section(r'(\s+)?ip protocol eigrp route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)')
+ if 'frr_zebra_config' in eigrp:
+ frr_cfg.add_before(frr.default_add_before, eigrp['frr_zebra_config'])
+ frr_cfg.commit_configuration(zebra_daemon)
+
+ # Generate empty helper string which can be ammended to FRR commands, it
+ # will be either empty (default VRF) or contain the "vrf <name" statement
+ vrf = ''
+ if 'vrf' in eigrp:
+ vrf = ' vrf ' + eigrp['vrf']
+
+ frr_cfg.load_configuration(eigrp_daemon)
+ frr_cfg.modify_section(f'^router eigrp \d+{vrf}', stop_pattern='^exit', remove_stop_mark=True)
+ if 'frr_eigrpd_config' in eigrp:
+ frr_cfg.add_before(frr.default_add_before, eigrp['frr_eigrpd_config'])
+ frr_cfg.commit_configuration(eigrp_daemon)
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/protocols_rip.py b/src/conf_mode/protocols_rip.py
index a76c1ce76..c78d90396 100755
--- a/src/conf_mode/protocols_rip.py
+++ b/src/conf_mode/protocols_rip.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021 VyOS maintainers and contributors
+# Copyright (C) 2021-2022 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
diff --git a/src/conf_mode/service_sla.py b/src/conf_mode/service_sla.py
new file mode 100755
index 000000000..e7c3ca59c
--- /dev/null
+++ b/src/conf_mode/service_sla.py
@@ -0,0 +1,113 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+import os
+
+from sys import exit
+
+from vyos.config import Config
+from vyos.configdict import dict_merge
+from vyos.template import render
+from vyos.util import call
+from vyos.xml import defaults
+from vyos import ConfigError
+from vyos import airbag
+airbag.enable()
+
+
+owamp_config_dir = '/etc/owamp-server'
+owamp_config_file = f'{owamp_config_dir}/owamp-server.conf'
+systemd_override_owamp = r'/etc/systemd/system/owamp-server.d/20-override.conf'
+
+twamp_config_dir = '/etc/twamp-server'
+twamp_config_file = f'{twamp_config_dir}/twamp-server.conf'
+systemd_override_twamp = r'/etc/systemd/system/twamp-server.d/20-override.conf'
+
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['service', 'sla']
+ if not conf.exists(base):
+ return None
+
+ sla = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+ # We have gathered the dict representation of the CLI, but there are default
+ # options which we need to update into the dictionary retrived.
+ default_values = defaults(base)
+ sla = dict_merge(default_values, sla)
+
+ # Ignore default XML values if config doesn't exists
+ # Delete key from dict
+ if not conf.exists(base + ['owamp-server']):
+ del sla['owamp_server']
+ if not conf.exists(base + ['twamp-server']):
+ del sla['twamp_server']
+
+ return sla
+
+def verify(sla):
+ if not sla:
+ return None
+
+def generate(sla):
+ if not sla:
+ return None
+
+ render(owamp_config_file, 'sla/owamp-server.conf.j2', sla)
+ render(systemd_override_owamp, 'sla/owamp-override.conf.j2', sla)
+
+ render(twamp_config_file, 'sla/twamp-server.conf.j2', sla)
+ render(systemd_override_twamp, 'sla/twamp-override.conf.j2', sla)
+
+ return None
+
+def apply(sla):
+ owamp_service = 'owamp-server.service'
+ twamp_service = 'twamp-server.service'
+
+ call('systemctl daemon-reload')
+
+ if not sla or 'owamp_server' not in sla:
+ call(f'systemctl stop {owamp_service}')
+
+ if os.path.exists(owamp_config_file):
+ os.unlink(owamp_config_file)
+
+ if not sla or 'twamp_server' not in sla:
+ call(f'systemctl stop {twamp_service}')
+ if os.path.exists(twamp_config_file):
+ os.unlink(twamp_config_file)
+
+ if sla and 'owamp_server' in sla:
+ call(f'systemctl reload-or-restart {owamp_service}')
+
+ if sla and 'twamp_server' in sla:
+ call(f'systemctl reload-or-restart {twamp_service}')
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/migration-scripts/policy/2-to-3 b/src/migration-scripts/policy/2-to-3
new file mode 100755
index 000000000..84cb1ff4a
--- /dev/null
+++ b/src/migration-scripts/policy/2-to-3
@@ -0,0 +1,58 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+# T3976: change cli
+# from: set policy route-map FOO rule 10 match ipv6 nexthop 'h:h:h:h:h:h:h:h'
+# to: set policy route-map FOO rule 10 match ipv6 nexthop address 'h:h:h:h:h:h:h:h'
+
+from sys import argv
+from sys import exit
+
+from vyos.configtree import ConfigTree
+
+if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+base = ['policy', 'route-map']
+config = ConfigTree(config_file)
+
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+
+for route_map in config.list_nodes(base):
+ if not config.exists(base + [route_map, 'rule']):
+ continue
+ for rule in config.list_nodes(base + [route_map, 'rule']):
+ base_rule = base + [route_map, 'rule', rule]
+
+ if config.exists(base_rule + ['match', 'ipv6', 'nexthop']):
+ tmp = config.return_value(base_rule + ['match', 'ipv6', 'nexthop'])
+ config.delete(base_rule + ['match', 'ipv6', 'nexthop'])
+ config.set(base_rule + ['match', 'ipv6', 'nexthop', 'address'], value=tmp)
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print(f'Failed to save the modified config: {e}')
+ exit(1) \ No newline at end of file
diff --git a/src/op_mode/pki.py b/src/op_mode/pki.py
index bc7813052..1e78c3a03 100755
--- a/src/op_mode/pki.py
+++ b/src/op_mode/pki.py
@@ -17,6 +17,7 @@
import argparse
import ipaddress
import os
+import re
import sys
import tabulate
@@ -30,7 +31,8 @@ from vyos.pki import encode_certificate, encode_public_key, encode_private_key,
from vyos.pki import create_certificate, create_certificate_request, create_certificate_revocation_list
from vyos.pki import create_private_key
from vyos.pki import create_dh_parameters
-from vyos.pki import load_certificate, load_certificate_request, load_private_key, load_crl
+from vyos.pki import load_certificate, load_certificate_request, load_private_key
+from vyos.pki import load_crl, load_dh_parameters, load_public_key
from vyos.pki import verify_certificate
from vyos.xml import defaults
from vyos.util import ask_input, ask_yes_no
@@ -183,13 +185,13 @@ def install_ssh_key(name, public_key, private_key, passphrase=None):
])
print(encode_private_key(private_key, encoding='PEM', key_format='OpenSSH', passphrase=passphrase))
-def install_keypair(name, key_type, private_key=None, public_key=None, passphrase=None):
+def install_keypair(name, key_type, private_key=None, public_key=None, passphrase=None, prompt=True):
# Show/install conf commands for key-pair
config_paths = []
if public_key:
- install_public_key = ask_yes_no('Do you want to install the public key?', default=True)
+ install_public_key = not prompt or ask_yes_no('Do you want to install the public key?', default=True)
public_key_pem = encode_public_key(public_key)
if install_public_key:
@@ -200,7 +202,7 @@ def install_keypair(name, key_type, private_key=None, public_key=None, passphras
print(public_key_pem)
if private_key:
- install_private_key = ask_yes_no('Do you want to install the private key?', default=True)
+ install_private_key = not prompt or ask_yes_no('Do you want to install the private key?', default=True)
private_key_pem = encode_private_key(private_key, passphrase=passphrase)
if install_private_key:
@@ -214,6 +216,13 @@ def install_keypair(name, key_type, private_key=None, public_key=None, passphras
install_into_config(conf, config_paths)
+def install_openvpn_key(name, key_data, key_version='1'):
+ config_paths = [
+ f"pki openvpn shared-secret {name} key '{key_data}'",
+ f"pki openvpn shared-secret {name} version '{key_version}'"
+ ]
+ install_into_config(conf, config_paths)
+
def install_wireguard_key(interface, private_key, public_key):
# Show conf commands for installing wireguard key pairs
from vyos.ifconfig import Section
@@ -640,15 +649,11 @@ def generate_openvpn_key(name, install=False, file=False):
key_data = "".join(key_lines[1:-1]) # Remove wrapper tags and line endings
key_version = '1'
- import re
version_search = re.search(r'BEGIN OpenVPN Static key V(\d+)', result) # Future-proofing (hopefully)
if version_search:
key_version = version_search[1]
- base = f"set pki openvpn shared-secret {name}"
- print("Configure mode commands to install OpenVPN key:")
- print(f"{base} key '{key_data}'")
- print(f"{base} version '{key_version}'")
+ install_openvpn_key(name, key_data, key_version)
if file:
write_file(f'{name}.key', result)
@@ -670,6 +675,167 @@ def generate_wireguard_psk(interface=None, peer=None, install=False):
else:
print(f'Pre-shared key: {psk}')
+# Import functions
+def import_ca_certificate(name, path=None, key_path=None):
+ if path:
+ if not os.path.exists(path):
+ print(f'File not found: {path}')
+ return
+
+ cert = None
+
+ with open(path) as f:
+ cert_data = f.read()
+ cert = load_certificate(cert_data, wrap_tags=False)
+
+ if not cert:
+ print(f'Invalid certificate: {path}')
+ return
+
+ install_certificate(name, cert, is_ca=True)
+
+ if key_path:
+ if not os.path.exists(key_path):
+ print(f'File not found: {key_path}')
+ return
+
+ key = None
+ passphrase = ask_input('Enter private key passphrase: ') or None
+
+ with open(key_path) as f:
+ key_data = f.read()
+ key = load_private_key(key_data, passphrase=passphrase, wrap_tags=False)
+
+ if not key:
+ print(f'Invalid private key or passphrase: {path}')
+ return
+
+ install_certificate(name, private_key=key, is_ca=True)
+
+def import_certificate(name, path=None, key_path=None):
+ if path:
+ if not os.path.exists(path):
+ print(f'File not found: {path}')
+ return
+
+ cert = None
+
+ with open(path) as f:
+ cert_data = f.read()
+ cert = load_certificate(cert_data, wrap_tags=False)
+
+ if not cert:
+ print(f'Invalid certificate: {path}')
+ return
+
+ install_certificate(name, cert, is_ca=False)
+
+ if key_path:
+ if not os.path.exists(key_path):
+ print(f'File not found: {key_path}')
+ return
+
+ key = None
+ passphrase = ask_input('Enter private key passphrase: ') or None
+
+ with open(key_path) as f:
+ key_data = f.read()
+ key = load_private_key(key_data, passphrase=passphrase, wrap_tags=False)
+
+ if not key:
+ print(f'Invalid private key or passphrase: {path}')
+ return
+
+ install_certificate(name, private_key=key, is_ca=False)
+
+def import_crl(name, path):
+ if not os.path.exists(path):
+ print(f'File not found: {path}')
+ return
+
+ crl = None
+
+ with open(path) as f:
+ crl_data = f.read()
+ crl = load_crl(crl_data, wrap_tags=False)
+
+ if not crl:
+ print(f'Invalid certificate: {path}')
+ return
+
+ install_crl(name, crl)
+
+def import_dh_parameters(name, path):
+ if not os.path.exists(path):
+ print(f'File not found: {path}')
+ return
+
+ dh = None
+
+ with open(path) as f:
+ dh_data = f.read()
+ dh = load_dh_parameters(dh_data, wrap_tags=False)
+
+ if not dh:
+ print(f'Invalid DH parameters: {path}')
+ return
+
+ install_dh_parameters(name, dh)
+
+def import_keypair(name, path=None, key_path=None):
+ if path:
+ if not os.path.exists(path):
+ print(f'File not found: {path}')
+ return
+
+ key = None
+
+ with open(path) as f:
+ key_data = f.read()
+ key = load_public_key(key_data, wrap_tags=False)
+
+ if not key:
+ print(f'Invalid public key: {path}')
+ return
+
+ install_keypair(name, None, public_key=key, prompt=False)
+
+ if key_path:
+ if not os.path.exists(key_path):
+ print(f'File not found: {key_path}')
+ return
+
+ key = None
+ passphrase = ask_input('Enter private key passphrase: ') or None
+
+ with open(key_path) as f:
+ key_data = f.read()
+ key = load_private_key(key_data, passphrase=passphrase, wrap_tags=False)
+
+ if not key:
+ print(f'Invalid private key or passphrase: {path}')
+ return
+
+ install_keypair(name, None, private_key=key, prompt=False)
+
+def import_openvpn_secret(name, path):
+ if not os.path.exists(path):
+ print(f'File not found: {path}')
+ return
+
+ key_data = None
+ key_version = '1'
+
+ with open(path) as f:
+ key_lines = f.read().split("\n")
+ key_data = "".join(key_lines[1:-1]) # Remove wrapper tags and line endings
+
+ version_search = re.search(r'BEGIN OpenVPN Static key V(\d+)', key_lines[0]) # Future-proofing (hopefully)
+ if version_search:
+ key_version = version_search[1]
+
+ install_openvpn_key(name, key_data, key_version)
+
# Show functions
def show_certificate_authority(name=None):
headers = ['Name', 'Subject', 'Issuer CN', 'Issued', 'Expiry', 'Private Key', 'Parent']
@@ -799,6 +965,9 @@ if __name__ == '__main__':
parser.add_argument('--file', help='Write generated keys into specified filename', action='store_true')
parser.add_argument('--install', help='Install generated keys into running-config', action='store_true')
+ parser.add_argument('--filename', help='Write certificate into specified filename', action='store')
+ parser.add_argument('--key-filename', help='Write key into specified filename', action='store')
+
args = parser.parse_args()
try:
@@ -840,7 +1009,19 @@ if __name__ == '__main__':
generate_wireguard_key(args.interface, install=args.install)
if args.psk:
generate_wireguard_psk(args.interface, peer=args.peer, install=args.install)
-
+ elif args.action == 'import':
+ if args.ca:
+ import_ca_certificate(args.ca, path=args.filename, key_path=args.key_filename)
+ elif args.certificate:
+ import_certificate(args.certificate, path=args.filename, key_path=args.key_filename)
+ elif args.crl:
+ import_crl(args.crl, args.filename)
+ elif args.dh:
+ import_dh_parameters(args.dh, args.filename)
+ elif args.keypair:
+ import_keypair(args.keypair, path=args.filename, key_path=args.key_filename)
+ elif args.openvpn:
+ import_openvpn_secret(args.openvpn, args.filename)
elif args.action == 'show':
if args.ca:
ca_name = None if args.ca == 'all' else args.ca
diff --git a/src/op_mode/show_neigh.py b/src/op_mode/show_neigh.py
index 94e745493..d874bd544 100755
--- a/src/op_mode/show_neigh.py
+++ b/src/op_mode/show_neigh.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020 VyOS maintainers and contributors
+# Copyright (C) 2022 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -14,83 +14,89 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#ip -j -f inet neigh list | jq
-#[
- #{
- #"dst": "192.168.101.8",
- #"dev": "enp0s25",
- #"lladdr": "78:d2:94:72:77:7e",
- #"state": [
- #"STALE"
- #]
- #},
- #{
- #"dst": "192.168.101.185",
- #"dev": "enp0s25",
- #"lladdr": "34:46:ec:76:f8:9b",
- #"state": [
- #"STALE"
- #]
- #},
- #{
- #"dst": "192.168.101.225",
- #"dev": "enp0s25",
- #"lladdr": "c2:cb:fa:bf:a0:35",
- #"state": [
- #"STALE"
- #]
- #},
- #{
- #"dst": "192.168.101.1",
- #"dev": "enp0s25",
- #"lladdr": "00:98:2b:f8:3f:11",
- #"state": [
- #"REACHABLE"
- #]
- #},
- #{
- #"dst": "192.168.101.181",
- #"dev": "enp0s25",
- #"lladdr": "d8:9b:3b:d5:88:22",
- #"state": [
- #"STALE"
- #]
- #}
-#]
+# Sample output of `ip --json neigh list`:
+#
+# [
+# {
+# "dst": "192.168.1.1",
+# "dev": "eth0", # Missing if `dev ...` option is used
+# "lladdr": "00:aa:bb:cc:dd:ee", # May be missing for failed entries
+# "state": [
+# "REACHABLE"
+# ]
+# },
+# ]
import sys
-import argparse
-import json
-from vyos.util import cmd
-
-def main():
- #parese args
- parser = argparse.ArgumentParser()
- parser.add_argument('--family', help='Protocol family', required=True)
- args = parser.parse_args()
-
- neigh_raw_json = cmd(f'ip -j -f {args.family} neigh list')
- neigh_raw_json = neigh_raw_json.lower()
- neigh_json = json.loads(neigh_raw_json)
-
- format_neigh = '%-50s %-10s %-20s %s'
- print(format_neigh % ("IP Address", "Device", "State", "LLADDR"))
- print(format_neigh % ("----------", "------", "-----", "------"))
-
- if neigh_json is not None:
- for neigh_item in neigh_json:
- dev = neigh_item['dev']
- dst = neigh_item['dst']
- lladdr = neigh_item['lladdr'] if 'lladdr' in neigh_item else ''
- state = neigh_item['state']
-
- i = 0
- for state_item in state:
- if i == 0:
- print(format_neigh % (dst, dev, state_item, lladdr))
- else:
- print(format_neigh % ('', '', state_item, ''))
- i+=1
-
+
+
+def get_raw_data(family, device=None, state=None):
+ from json import loads
+ from vyos.util import cmd
+
+ if device:
+ device = f"dev {device}"
+ else:
+ device = ""
+
+ if state:
+ state = f"nud {state}"
+ else:
+ state = ""
+
+ neigh_cmd = f"ip --family {family} --json neighbor list {device} {state}"
+
+ data = loads(cmd(neigh_cmd))
+
+ return data
+
+def get_formatted_output(family, device=None, state=None):
+ from tabulate import tabulate
+
+ def entry_to_list(e, intf=None):
+ dst = e["dst"]
+
+ # State is always a list in the iproute2 output
+ state = ", ".join(e["state"])
+
+ # Link layer address is absent from e.g. FAILED entries
+ if "lladdr" in e:
+ lladdr = e["lladdr"]
+ else:
+ lladdr = None
+
+ # Device field is absent from outputs of `ip neigh list dev ...`
+ if "dev" in e:
+ dev = e["dev"]
+ elif device:
+ dev = device
+ else:
+ raise ValueError("interface is not defined")
+
+ return [dst, dev, lladdr, state]
+
+ neighs = get_raw_data(family, device=device, state=state)
+ neighs = map(entry_to_list, neighs)
+
+ headers = ["Address", "Interface", "Link layer address", "State"]
+ return tabulate(neighs, headers)
+
if __name__ == '__main__':
- main()
+ from argparse import ArgumentParser
+
+ parser = ArgumentParser()
+ parser.add_argument("-f", "--family", type=str, default="inet", help="Address family")
+ parser.add_argument("-i", "--interface", type=str, help="Network interface")
+ parser.add_argument("-s", "--state", type=str, help="Neighbor table entry state")
+
+ args = parser.parse_args()
+
+ if args.state:
+ if args.state not in ["reachable", "failed", "stale", "permanent"]:
+ raise ValueError(f"""Incorrect state "{args.state}"! Must be one of: reachable, stale, failed, permanent""")
+
+ try:
+ print(get_formatted_output(args.family, device=args.interface, state=args.state))
+ except ValueError as e:
+ print(e)
+ sys.exit(1)