diff options
author | sarthurdev <965089+sarthurdev@users.noreply.github.com> | 2021-08-01 00:13:47 +0200 |
---|---|---|
committer | sarthurdev <965089+sarthurdev@users.noreply.github.com> | 2021-12-06 21:20:49 +0100 |
commit | f86041de88c3b0e0ce9ecc6d2cbc309bc8cb28e2 (patch) | |
tree | 99632f4cb67bfa8435288249e9142439b869fd19 | |
parent | 3ebb08893b4bbd016bd0eb04374be5f691ad4abb (diff) | |
download | vyos-1x-f86041de88c3b0e0ce9ecc6d2cbc309bc8cb28e2.tar.gz vyos-1x-f86041de88c3b0e0ce9ecc6d2cbc309bc8cb28e2.zip |
policy: T2199: Migrate policy route to XML/Python
30 files changed, 1640 insertions, 0 deletions
diff --git a/data/templates/firewall/nftables-policy.tmpl b/data/templates/firewall/nftables-policy.tmpl new file mode 100644 index 000000000..aa6bb6fc1 --- /dev/null +++ b/data/templates/firewall/nftables-policy.tmpl @@ -0,0 +1,53 @@ +#!/usr/sbin/nft -f + +table ip mangle { +{% if first_install is defined %} + chain VYOS_PBR_PREROUTING { + type filter hook prerouting priority -150; policy accept; + } + chain VYOS_PBR_POSTROUTING { + type filter hook postrouting priority -150; policy accept; + } +{% endif %} +{% if route is defined -%} +{% for route_text, conf in route.items() %} + chain VYOS_PBR_{{ route_text }} { +{% if conf.rule is defined %} +{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not defined %} + {{ rule_conf | nft_rule(route_text, rule_id, 'ip') }} +{% endfor %} +{% endif %} +{% if conf.default_action is defined %} + counter {{ conf.default_action | nft_action }} comment "{{ name_text }} default-action {{ conf.default_action }}" +{% else %} + counter return +{% endif %} + } +{% endfor %} +{%- endif %} +} + +table ip6 mangle { +{% if first_install is defined %} + chain VYOS_PBR6_PREROUTING { + type filter hook prerouting priority -150; policy accept; + } + chain VYOS_PBR6_POSTROUTING { + type filter hook postrouting priority -150; policy accept; + } +{% endif %} +{% if ipv6_route is defined %} +{% for route_text, conf in ipv6_route.items() %} + chain VYOS_PBR6_{{ route_text }} { +{% if conf.rule is defined %} +{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not defined %} + {{ rule_conf | nft_rule(route_text, rule_id, 'ip6') }} +{% endfor %} +{% endif %} +{% if conf.default_action is defined %} + counter {{ conf.default_action | nft_action }} comment "{{ name_text }} default-action {{ conf.default_action }}" +{% endif %} + } +{% endfor %} +{% endif %} +} diff --git a/interface-definitions/include/interface/interface-policy-vif-c.xml.i b/interface-definitions/include/interface/interface-policy-vif-c.xml.i new file mode 100644 index 000000000..5dad6422b --- /dev/null +++ b/interface-definitions/include/interface/interface-policy-vif-c.xml.i @@ -0,0 +1,26 @@ +<!-- include start from interface/interface-policy-vif-c.xml.i --> +<node name="policy" owner="${vyos_conf_scripts_dir}/policy-route-interface.py $VAR(../../../@).$VAR(../../@).$VAR(../@)"> + <properties> + <priority>620</priority> + <help>Policy route options</help> + </properties> + <children> + <leafNode name="route"> + <properties> + <help>IPv4 policy route ruleset for interface</help> + <completionHelp> + <path>policy route</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="ipv6-route"> + <properties> + <help>IPv6 policy route ruleset for interface</help> + <completionHelp> + <path>policy ipv6-route</path> + </completionHelp> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/interface/interface-policy-vif.xml.i b/interface-definitions/include/interface/interface-policy-vif.xml.i new file mode 100644 index 000000000..5ee80ae13 --- /dev/null +++ b/interface-definitions/include/interface/interface-policy-vif.xml.i @@ -0,0 +1,26 @@ +<!-- include start from interface/interface-policy-vif.xml.i --> +<node name="policy" owner="${vyos_conf_scripts_dir}/policy-route-interface.py $VAR(../../@).$VAR(../@)"> + <properties> + <priority>620</priority> + <help>Policy route options</help> + </properties> + <children> + <leafNode name="route"> + <properties> + <help>IPv4 policy route ruleset for interface</help> + <completionHelp> + <path>policy route</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="ipv6-route"> + <properties> + <help>IPv6 policy route ruleset for interface</help> + <completionHelp> + <path>policy ipv6-route</path> + </completionHelp> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/interface/interface-policy.xml.i b/interface-definitions/include/interface/interface-policy.xml.i new file mode 100644 index 000000000..06f025af1 --- /dev/null +++ b/interface-definitions/include/interface/interface-policy.xml.i @@ -0,0 +1,26 @@ +<!-- include start from interface/interface-policy.xml.i --> +<node name="policy" owner="${vyos_conf_scripts_dir}/policy-route-interface.py $VAR(../@)"> + <properties> + <priority>620</priority> + <help>Policy route options</help> + </properties> + <children> + <leafNode name="route"> + <properties> + <help>IPv4 policy route ruleset for interface</help> + <completionHelp> + <path>policy route</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="ipv6-route"> + <properties> + <help>IPv6 policy route ruleset for interface</help> + <completionHelp> + <path>policy ipv6-route</path> + </completionHelp> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/interface/vif-s.xml.i b/interface-definitions/include/interface/vif-s.xml.i index caa5248ab..f1a61ff64 100644 --- a/interface-definitions/include/interface/vif-s.xml.i +++ b/interface-definitions/include/interface/vif-s.xml.i @@ -19,6 +19,7 @@ #include <include/interface/disable-link-detect.xml.i> #include <include/interface/disable.xml.i> #include <include/interface/interface-firewall-vif.xml.i> + #include <include/interface/interface-policy-vif.xml.i> <leafNode name="protocol"> <properties> <help>Protocol used for service VLAN (default: 802.1ad)</help> @@ -65,6 +66,7 @@ #include <include/interface/mtu-68-16000.xml.i> #include <include/interface/vrf.xml.i> #include <include/interface/interface-firewall-vif-c.xml.i> + #include <include/interface/interface-policy-vif-c.xml.i> </children> </tagNode> #include <include/interface/vrf.xml.i> diff --git a/interface-definitions/include/interface/vif.xml.i b/interface-definitions/include/interface/vif.xml.i index a2382cc1b..11ba7e2f8 100644 --- a/interface-definitions/include/interface/vif.xml.i +++ b/interface-definitions/include/interface/vif.xml.i @@ -20,6 +20,7 @@ #include <include/interface/disable.xml.i> #include <include/interface/vrf.xml.i> #include <include/interface/interface-firewall-vif.xml.i> + #include <include/interface/interface-policy-vif.xml.i> <leafNode name="egress-qos"> <properties> <help>VLAN egress QoS</help> diff --git a/interface-definitions/include/policy/route-common-rule-ipv6.xml.i b/interface-definitions/include/policy/route-common-rule-ipv6.xml.i new file mode 100644 index 000000000..2d6adcd1d --- /dev/null +++ b/interface-definitions/include/policy/route-common-rule-ipv6.xml.i @@ -0,0 +1,569 @@ +<!-- include start from policy/route-common-rule.xml.i --> +#include <include/policy/route-rule-action.xml.i> +#include <include/generic-description.xml.i> +<leafNode name="disable"> + <properties> + <help>Option to disable firewall rule</help> + <valueless/> + </properties> +</leafNode> +<node name="fragment"> + <properties> + <help>IP fragment match</help> + </properties> + <children> + <leafNode name="match-frag"> + <properties> + <help>Second and further fragments of fragmented packets</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="match-non-frag"> + <properties> + <help>Head fragments or unfragmented packets</help> + <valueless/> + </properties> + </leafNode> + </children> +</node> +<node name="ipsec"> + <properties> + <help>Inbound IPsec packets</help> + </properties> + <children> + <leafNode name="match-ipsec"> + <properties> + <help>Inbound IPsec packets</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="match-none"> + <properties> + <help>Inbound non-IPsec packets</help> + <valueless/> + </properties> + </leafNode> + </children> +</node> +<node name="limit"> + <properties> + <help>Rate limit using a token bucket filter</help> + </properties> + <children> + <leafNode name="burst"> + <properties> + <help>Maximum number of packets to allow in excess of rate</help> + <valueHelp> + <format>u32:0-4294967295</format> + <description>Maximum number of packets to allow in excess of rate</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + </properties> + </leafNode> + <leafNode name="rate"> + <properties> + <help>Maximum average matching rate</help> + <valueHelp> + <format>u32:0-4294967295</format> + <description>Maximum average matching rate</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<leafNode name="log"> + <properties> + <help>Option to log packets matching rule</help> + <completionHelp> + <list>enable disable</list> + </completionHelp> + <valueHelp> + <format>enable</format> + <description>Enable log</description> + </valueHelp> + <valueHelp> + <format>disable</format> + <description>Disable log</description> + </valueHelp> + <constraint> + <regex>^(enable|disable)$</regex> + </constraint> + </properties> +</leafNode> +<leafNode name="protocol"> + <properties> + <help>Protocol to match (protocol name, number, or "all")</help> + <completionHelp> + <script>cat /etc/protocols | sed -e '/^#.*/d' | awk '{ print $1 }'</script> + </completionHelp> + <valueHelp> + <format>all</format> + <description>All IP protocols</description> + </valueHelp> + <valueHelp> + <format>tcp_udp</format> + <description>Both TCP and UDP</description> + </valueHelp> + <valueHelp> + <format>0-255</format> + <description>IP protocol number</description> + </valueHelp> + <valueHelp> + <format>!<protocol></format> + <description>IP protocol number</description> + </valueHelp> + <constraint> + <validator name="ip-protocol"/> + </constraint> + </properties> + <defaultValue>all</defaultValue> +</leafNode> +<node name="recent"> + <properties> + <help>Parameters for matching recently seen sources</help> + </properties> + <children> + <leafNode name="count"> + <properties> + <help>Source addresses seen more than N times</help> + <valueHelp> + <format>u32:1-255</format> + <description>Source addresses seen more than N times</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + <leafNode name="time"> + <properties> + <help>Source addresses seen in the last N seconds</help> + <valueHelp> + <format>u32:0-4294967295</format> + <description>Source addresses seen in the last N seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<node name="set"> + <properties> + <help>Packet modifications</help> + </properties> + <children> + <leafNode name="dscp"> + <properties> + <help>Packet Differentiated Services Codepoint (DSCP)</help> + <valueHelp> + <format>u32:0-63</format> + <description>DSCP number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-63"/> + </constraint> + </properties> + </leafNode> + <leafNode name="mark"> + <properties> + <help>Packet marking</help> + <valueHelp> + <format>u32:1-2147483647</format> + <description>Packet marking</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-2147483647"/> + </constraint> + </properties> + </leafNode> + <leafNode name="table"> + <properties> + <help>Routing table to forward packet with</help> + <valueHelp> + <format>u32:1-200</format> + <description>Table number</description> + </valueHelp> + <valueHelp> + <format>main</format> + <description>Main table</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-200"/> + <regex>^(main)$</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="tcp-mss"> + <properties> + <help>TCP Maximum Segment Size</help> + <valueHelp> + <format>u32:500-1460</format> + <description>Explicitly set TCP MSS value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 500-1460"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<node name="source"> + <properties> + <help>Source parameters</help> + </properties> + <children> + #include <include/firewall/address-ipv6.xml.i> + #include <include/firewall/source-destination-group.xml.i> + <leafNode name="mac-address"> + <properties> + <help>Source MAC address</help> + <valueHelp> + <format><MAC address></format> + <description>MAC address to match</description> + </valueHelp> + <valueHelp> + <format>!<MAC address></format> + <description>Match everything except the specified MAC address</description> + </valueHelp> + </properties> + </leafNode> + #include <include/firewall/port.xml.i> + </children> +</node> +<node name="state"> + <properties> + <help>Session state</help> + </properties> + <children> + <leafNode name="established"> + <properties> + <help>Established state</help> + <completionHelp> + <list>enable disable</list> + </completionHelp> + <valueHelp> + <format>enable</format> + <description>Enable</description> + </valueHelp> + <valueHelp> + <format>disable</format> + <description>Disable</description> + </valueHelp> + <constraint> + <regex>^(enable|disable)$</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="invalid"> + <properties> + <help>Invalid state</help> + <completionHelp> + <list>enable disable</list> + </completionHelp> + <valueHelp> + <format>enable</format> + <description>Enable</description> + </valueHelp> + <valueHelp> + <format>disable</format> + <description>Disable</description> + </valueHelp> + <constraint> + <regex>^(enable|disable)$</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="new"> + <properties> + <help>New state</help> + <completionHelp> + <list>enable disable</list> + </completionHelp> + <valueHelp> + <format>enable</format> + <description>Enable</description> + </valueHelp> + <valueHelp> + <format>disable</format> + <description>Disable</description> + </valueHelp> + <constraint> + <regex>^(enable|disable)$</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="related"> + <properties> + <help>Related state</help> + <completionHelp> + <list>enable disable</list> + </completionHelp> + <valueHelp> + <format>enable</format> + <description>Enable</description> + </valueHelp> + <valueHelp> + <format>disable</format> + <description>Disable</description> + </valueHelp> + <constraint> + <regex>^(enable|disable)$</regex> + </constraint> + </properties> + </leafNode> + </children> +</node> +<node name="tcp"> + <properties> + <help>TCP flags to match</help> + </properties> + <children> + <leafNode name="flags"> + <properties> + <help>TCP flags to match</help> + <valueHelp> + <format>txt</format> + <description>TCP flags to match</description> + </valueHelp> + <valueHelp> + <format> </format> + <description>\n\n Allowed values for TCP flags : SYN ACK FIN RST URG PSH ALL\n When specifying more than one flag, flags should be comma-separated.\n For example : value of 'SYN,!ACK,!FIN,!RST' will only match packets with\n the SYN flag set, and the ACK, FIN and RST flags unset</description> + </valueHelp> + </properties> + </leafNode> + </children> +</node> +<node name="time"> + <properties> + <help>Time to match rule</help> + </properties> + <children> + <leafNode name="monthdays"> + <properties> + <help>Monthdays to match rule on</help> + </properties> + </leafNode> + <leafNode name="startdate"> + <properties> + <help>Date to start matching rule</help> + </properties> + </leafNode> + <leafNode name="starttime"> + <properties> + <help>Time of day to start matching rule</help> + </properties> + </leafNode> + <leafNode name="stopdate"> + <properties> + <help>Date to stop matching rule</help> + </properties> + </leafNode> + <leafNode name="stoptime"> + <properties> + <help>Time of day to stop matching rule</help> + </properties> + </leafNode> + <leafNode name="utc"> + <properties> + <help>Interpret times for startdate, stopdate, starttime and stoptime to be UTC</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="weekdays"> + <properties> + <help>Weekdays to match rule on</help> + </properties> + </leafNode> + </children> +</node> +<node name="icmpv6"> + <properties> + <help>ICMPv6 type and code information</help> + </properties> + <children> + <leafNode name="type"> + <properties> + <help>ICMP type-name</help> + <completionHelp> + <list>any echo-reply pong destination-unreachable network-unreachable host-unreachable protocol-unreachable port-unreachable fragmentation-needed source-route-failed network-unknown host-unknown network-prohibited host-prohibited TOS-network-unreachable TOS-host-unreachable communication-prohibited host-precedence-violation precedence-cutoff source-quench redirect network-redirect host-redirect TOS-network-redirect TOS host-redirect echo-request ping router-advertisement router-solicitation time-exceeded ttl-exceeded ttl-zero-during-transit ttl-zero-during-reassembly parameter-problem ip-header-bad required-option-missing timestamp-request timestamp-reply address-mask-request address-mask-reply packet-too-big</list> + </completionHelp> + <valueHelp> + <format>any</format> + <description>Any ICMP type/code</description> + </valueHelp> + <valueHelp> + <format>echo-reply</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>pong</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>destination-unreachable</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>network-unreachable</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>host-unreachable</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>protocol-unreachable</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>port-unreachable</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>fragmentation-needed</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>source-route-failed</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>network-unknown</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>host-unknown</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>network-prohibited</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>host-prohibited</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>TOS-network-unreachable</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>TOS-host-unreachable</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>communication-prohibited</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>host-precedence-violation</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>precedence-cutoff</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>source-quench</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>redirect</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>network-redirect</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>host-redirect</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>TOS-network-redirect</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>TOS host-redirect</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>echo-request</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>ping</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>router-advertisement</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>router-solicitation</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>time-exceeded</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>ttl-exceeded</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>ttl-zero-during-transit</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>ttl-zero-during-reassembly</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>parameter-problem</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>ip-header-bad</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>required-option-missing</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>timestamp-request</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>timestamp-reply</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>address-mask-request</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>address-mask-reply</format> + <description>ICMP type/code name</description> + </valueHelp> + <valueHelp> + <format>packet-too-big</format> + <description>ICMP type/code name</description> + </valueHelp> + <constraint> + <regex>^(any|echo-reply|pong|destination-unreachable|network-unreachable|host-unreachable|protocol-unreachable|port-unreachable|fragmentation-needed|source-route-failed|network-unknown|host-unknown|network-prohibited|host-prohibited|TOS-network-unreachable|TOS-host-unreachable|communication-prohibited|host-precedence-violation|precedence-cutoff|source-quench|redirect|network-redirect|host-redirect|TOS-network-redirect|TOS host-redirect|echo-request|ping|router-advertisement|router-solicitation|time-exceeded|ttl-exceeded|ttl-zero-during-transit|ttl-zero-during-reassembly|parameter-problem|ip-header-bad|required-option-missing|timestamp-request|timestamp-reply|address-mask-request|address-mask-reply|packet-too-big)$</regex> + <validator name="numeric" argument="--range 0-255"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/policy/route-common-rule.xml.i b/interface-definitions/include/policy/route-common-rule.xml.i new file mode 100644 index 000000000..c4deefd2a --- /dev/null +++ b/interface-definitions/include/policy/route-common-rule.xml.i @@ -0,0 +1,418 @@ +<!-- include start from policy/route-common-rule.xml.i --> +#include <include/policy/route-rule-action.xml.i> +#include <include/generic-description.xml.i> +<leafNode name="disable"> + <properties> + <help>Option to disable firewall rule</help> + <valueless/> + </properties> +</leafNode> +<node name="fragment"> + <properties> + <help>IP fragment match</help> + </properties> + <children> + <leafNode name="match-frag"> + <properties> + <help>Second and further fragments of fragmented packets</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="match-non-frag"> + <properties> + <help>Head fragments or unfragmented packets</help> + <valueless/> + </properties> + </leafNode> + </children> +</node> +<node name="ipsec"> + <properties> + <help>Inbound IPsec packets</help> + </properties> + <children> + <leafNode name="match-ipsec"> + <properties> + <help>Inbound IPsec packets</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="match-none"> + <properties> + <help>Inbound non-IPsec packets</help> + <valueless/> + </properties> + </leafNode> + </children> +</node> +<node name="limit"> + <properties> + <help>Rate limit using a token bucket filter</help> + </properties> + <children> + <leafNode name="burst"> + <properties> + <help>Maximum number of packets to allow in excess of rate</help> + <valueHelp> + <format>u32:0-4294967295</format> + <description>Maximum number of packets to allow in excess of rate</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + </properties> + </leafNode> + <leafNode name="rate"> + <properties> + <help>Maximum average matching rate</help> + <valueHelp> + <format>u32:0-4294967295</format> + <description>Maximum average matching rate</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<leafNode name="log"> + <properties> + <help>Option to log packets matching rule</help> + <completionHelp> + <list>enable disable</list> + </completionHelp> + <valueHelp> + <format>enable</format> + <description>Enable log</description> + </valueHelp> + <valueHelp> + <format>disable</format> + <description>Disable log</description> + </valueHelp> + <constraint> + <regex>^(enable|disable)$</regex> + </constraint> + </properties> +</leafNode> +<leafNode name="protocol"> + <properties> + <help>Protocol to match (protocol name, number, or "all")</help> + <completionHelp> + <script>cat /etc/protocols | sed -e '/^#.*/d' | awk '{ print $1 }'</script> + </completionHelp> + <valueHelp> + <format>all</format> + <description>All IP protocols</description> + </valueHelp> + <valueHelp> + <format>tcp_udp</format> + <description>Both TCP and UDP</description> + </valueHelp> + <valueHelp> + <format>0-255</format> + <description>IP protocol number</description> + </valueHelp> + <valueHelp> + <format>!<protocol></format> + <description>IP protocol number</description> + </valueHelp> + <constraint> + <validator name="ip-protocol"/> + </constraint> + </properties> + <defaultValue>all</defaultValue> +</leafNode> +<node name="recent"> + <properties> + <help>Parameters for matching recently seen sources</help> + </properties> + <children> + <leafNode name="count"> + <properties> + <help>Source addresses seen more than N times</help> + <valueHelp> + <format>u32:1-255</format> + <description>Source addresses seen more than N times</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + <leafNode name="time"> + <properties> + <help>Source addresses seen in the last N seconds</help> + <valueHelp> + <format>u32:0-4294967295</format> + <description>Source addresses seen in the last N seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<node name="set"> + <properties> + <help>Packet modifications</help> + </properties> + <children> + <leafNode name="dscp"> + <properties> + <help>Packet Differentiated Services Codepoint (DSCP)</help> + <valueHelp> + <format>u32:0-63</format> + <description>DSCP number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-63"/> + </constraint> + </properties> + </leafNode> + <leafNode name="mark"> + <properties> + <help>Packet marking</help> + <valueHelp> + <format>u32:1-2147483647</format> + <description>Packet marking</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-2147483647"/> + </constraint> + </properties> + </leafNode> + <leafNode name="table"> + <properties> + <help>Routing table to forward packet with</help> + <valueHelp> + <format>u32:1-200</format> + <description>Table number</description> + </valueHelp> + <valueHelp> + <format>main</format> + <description>Main table</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-200"/> + <regex>^(main)$</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="tcp-mss"> + <properties> + <help>TCP Maximum Segment Size</help> + <valueHelp> + <format>u32:500-1460</format> + <description>Explicitly set TCP MSS value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 500-1460"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<node name="source"> + <properties> + <help>Source parameters</help> + </properties> + <children> + #include <include/firewall/address.xml.i> + #include <include/firewall/source-destination-group.xml.i> + <leafNode name="mac-address"> + <properties> + <help>Source MAC address</help> + <valueHelp> + <format><MAC address></format> + <description>MAC address to match</description> + </valueHelp> + <valueHelp> + <format>!<MAC address></format> + <description>Match everything except the specified MAC address</description> + </valueHelp> + </properties> + </leafNode> + #include <include/firewall/port.xml.i> + </children> +</node> +<node name="state"> + <properties> + <help>Session state</help> + </properties> + <children> + <leafNode name="established"> + <properties> + <help>Established state</help> + <completionHelp> + <list>enable disable</list> + </completionHelp> + <valueHelp> + <format>enable</format> + <description>Enable</description> + </valueHelp> + <valueHelp> + <format>disable</format> + <description>Disable</description> + </valueHelp> + <constraint> + <regex>^(enable|disable)$</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="invalid"> + <properties> + <help>Invalid state</help> + <completionHelp> + <list>enable disable</list> + </completionHelp> + <valueHelp> + <format>enable</format> + <description>Enable</description> + </valueHelp> + <valueHelp> + <format>disable</format> + <description>Disable</description> + </valueHelp> + <constraint> + <regex>^(enable|disable)$</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="new"> + <properties> + <help>New state</help> + <completionHelp> + <list>enable disable</list> + </completionHelp> + <valueHelp> + <format>enable</format> + <description>Enable</description> + </valueHelp> + <valueHelp> + <format>disable</format> + <description>Disable</description> + </valueHelp> + <constraint> + <regex>^(enable|disable)$</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="related"> + <properties> + <help>Related state</help> + <completionHelp> + <list>enable disable</list> + </completionHelp> + <valueHelp> + <format>enable</format> + <description>Enable</description> + </valueHelp> + <valueHelp> + <format>disable</format> + <description>Disable</description> + </valueHelp> + <constraint> + <regex>^(enable|disable)$</regex> + </constraint> + </properties> + </leafNode> + </children> +</node> +<node name="tcp"> + <properties> + <help>TCP flags to match</help> + </properties> + <children> + <leafNode name="flags"> + <properties> + <help>TCP flags to match</help> + <valueHelp> + <format>txt</format> + <description>TCP flags to match</description> + </valueHelp> + <valueHelp> + <format> </format> + <description>\n\n Allowed values for TCP flags : SYN ACK FIN RST URG PSH ALL\n When specifying more than one flag, flags should be comma-separated.\n For example : value of 'SYN,!ACK,!FIN,!RST' will only match packets with\n the SYN flag set, and the ACK, FIN and RST flags unset</description> + </valueHelp> + </properties> + </leafNode> + </children> +</node> +<node name="time"> + <properties> + <help>Time to match rule</help> + </properties> + <children> + <leafNode name="monthdays"> + <properties> + <help>Monthdays to match rule on</help> + </properties> + </leafNode> + <leafNode name="startdate"> + <properties> + <help>Date to start matching rule</help> + </properties> + </leafNode> + <leafNode name="starttime"> + <properties> + <help>Time of day to start matching rule</help> + </properties> + </leafNode> + <leafNode name="stopdate"> + <properties> + <help>Date to stop matching rule</help> + </properties> + </leafNode> + <leafNode name="stoptime"> + <properties> + <help>Time of day to stop matching rule</help> + </properties> + </leafNode> + <leafNode name="utc"> + <properties> + <help>Interpret times for startdate, stopdate, starttime and stoptime to be UTC</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="weekdays"> + <properties> + <help>Weekdays to match rule on</help> + </properties> + </leafNode> + </children> +</node> +<node name="icmp"> + <properties> + <help>ICMP type and code information</help> + </properties> + <children> + <leafNode name="code"> + <properties> + <help>ICMP code (0-255)</help> + <valueHelp> + <format>u32:0-255</format> + <description>ICMP code (0-255)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-255"/> + </constraint> + </properties> + </leafNode> + <leafNode name="type"> + <properties> + <help>ICMP type (0-255)</help> + <valueHelp> + <format>u32:0-255</format> + <description>ICMP type (0-255)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-255"/> + </constraint> + </properties> + </leafNode> + #include <include/firewall/icmp-type-name.xml.i> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/policy/route-rule-action.xml.i b/interface-definitions/include/policy/route-rule-action.xml.i new file mode 100644 index 000000000..9c880579d --- /dev/null +++ b/interface-definitions/include/policy/route-rule-action.xml.i @@ -0,0 +1,17 @@ +<!-- include start from policy/route-rule-action.xml.i --> +<leafNode name="action"> + <properties> + <help>Rule action [REQUIRED]</help> + <completionHelp> + <list>drop</list> + </completionHelp> + <valueHelp> + <format>drop</format> + <description>Drop matching entries</description> + </valueHelp> + <constraint> + <regex>^(drop)$</regex> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/interfaces-bonding.xml.in b/interface-definitions/interfaces-bonding.xml.in index 03cbb523d..723041ca5 100644 --- a/interface-definitions/interfaces-bonding.xml.in +++ b/interface-definitions/interfaces-bonding.xml.in @@ -57,6 +57,7 @@ #include <include/interface/vrf.xml.i> #include <include/interface/mirror.xml.i> #include <include/interface/interface-firewall.xml.i> + #include <include/interface/interface-policy.xml.i> <leafNode name="hash-policy"> <properties> <help>Bonding transmit hash policy</help> diff --git a/interface-definitions/interfaces-bridge.xml.in b/interface-definitions/interfaces-bridge.xml.in index ebf6c3631..0856615be 100644 --- a/interface-definitions/interfaces-bridge.xml.in +++ b/interface-definitions/interfaces-bridge.xml.in @@ -42,6 +42,7 @@ #include <include/interface/vrf.xml.i> #include <include/interface/mtu-68-16000.xml.i> #include <include/interface/interface-firewall.xml.i> + #include <include/interface/interface-policy.xml.i> <leafNode name="forwarding-delay"> <properties> <help>Forwarding delay</help> diff --git a/interface-definitions/interfaces-dummy.xml.in b/interface-definitions/interfaces-dummy.xml.in index c6061b8bb..1231b1492 100644 --- a/interface-definitions/interfaces-dummy.xml.in +++ b/interface-definitions/interfaces-dummy.xml.in @@ -20,6 +20,7 @@ #include <include/interface/description.xml.i> #include <include/interface/disable.xml.i> #include <include/interface/interface-firewall.xml.i> + #include <include/interface/interface-policy.xml.i> <node name="ip"> <properties> <help>IPv4 routing parameters</help> diff --git a/interface-definitions/interfaces-ethernet.xml.in b/interface-definitions/interfaces-ethernet.xml.in index 3868ebbbc..9e113cb71 100644 --- a/interface-definitions/interfaces-ethernet.xml.in +++ b/interface-definitions/interfaces-ethernet.xml.in @@ -32,6 +32,7 @@ #include <include/interface/disable-link-detect.xml.i> #include <include/interface/disable.xml.i> #include <include/interface/interface-firewall.xml.i> + #include <include/interface/interface-policy.xml.i> <leafNode name="duplex"> <properties> <help>Duplex mode</help> diff --git a/interface-definitions/interfaces-geneve.xml.in b/interface-definitions/interfaces-geneve.xml.in index 06ad7c82b..dd4d324d4 100644 --- a/interface-definitions/interfaces-geneve.xml.in +++ b/interface-definitions/interfaces-geneve.xml.in @@ -24,6 +24,7 @@ #include <include/interface/mac.xml.i> #include <include/interface/mtu-1450-16000.xml.i> #include <include/interface/interface-firewall.xml.i> + #include <include/interface/interface-policy.xml.i> <node name="parameters"> <properties> <help>GENEVE tunnel parameters</help> diff --git a/interface-definitions/interfaces-l2tpv3.xml.in b/interface-definitions/interfaces-l2tpv3.xml.in index c5bca4408..85d4ab992 100644 --- a/interface-definitions/interfaces-l2tpv3.xml.in +++ b/interface-definitions/interfaces-l2tpv3.xml.in @@ -33,6 +33,7 @@ </leafNode> #include <include/interface/disable.xml.i> #include <include/interface/interface-firewall.xml.i> + #include <include/interface/interface-policy.xml.i> <leafNode name="encapsulation"> <properties> <help>Encapsulation type (default: UDP)</help> diff --git a/interface-definitions/interfaces-macsec.xml.in b/interface-definitions/interfaces-macsec.xml.in index 5713d985b..d69a093af 100644 --- a/interface-definitions/interfaces-macsec.xml.in +++ b/interface-definitions/interfaces-macsec.xml.in @@ -20,6 +20,7 @@ #include <include/interface/ipv4-options.xml.i> #include <include/interface/ipv6-options.xml.i> #include <include/interface/interface-firewall.xml.i> + #include <include/interface/interface-policy.xml.i> <node name="security"> <properties> <help>Security/Encryption Settings</help> diff --git a/interface-definitions/interfaces-openvpn.xml.in b/interface-definitions/interfaces-openvpn.xml.in index 1fe8e63f8..16d91145f 100644 --- a/interface-definitions/interfaces-openvpn.xml.in +++ b/interface-definitions/interfaces-openvpn.xml.in @@ -35,6 +35,7 @@ </node> #include <include/interface/description.xml.i> #include <include/interface/interface-firewall.xml.i> + #include <include/interface/interface-policy.xml.i> <leafNode name="device-type"> <properties> <help>OpenVPN interface device-type (default: tun)</help> diff --git a/interface-definitions/interfaces-pppoe.xml.in b/interface-definitions/interfaces-pppoe.xml.in index d9c30031e..80a890940 100644 --- a/interface-definitions/interfaces-pppoe.xml.in +++ b/interface-definitions/interfaces-pppoe.xml.in @@ -20,6 +20,7 @@ #include <include/interface/authentication.xml.i> #include <include/interface/dial-on-demand.xml.i> #include <include/interface/interface-firewall.xml.i> + #include <include/interface/interface-policy.xml.i> <leafNode name="default-route"> <properties> <help>Default route insertion behaviour (default: auto)</help> diff --git a/interface-definitions/interfaces-pseudo-ethernet.xml.in b/interface-definitions/interfaces-pseudo-ethernet.xml.in index 974ba1a50..bf7055f8d 100644 --- a/interface-definitions/interfaces-pseudo-ethernet.xml.in +++ b/interface-definitions/interfaces-pseudo-ethernet.xml.in @@ -28,6 +28,7 @@ #include <include/source-interface-ethernet.xml.i> #include <include/interface/mac.xml.i> #include <include/interface/interface-firewall.xml.i> + #include <include/interface/interface-policy.xml.i> <leafNode name="mode"> <properties> <help>Receive mode (default: private)</help> diff --git a/interface-definitions/interfaces-tunnel.xml.in b/interface-definitions/interfaces-tunnel.xml.in index b95f07a4b..e08b2fdab 100644 --- a/interface-definitions/interfaces-tunnel.xml.in +++ b/interface-definitions/interfaces-tunnel.xml.in @@ -31,6 +31,7 @@ #include <include/interface/tunnel-remote.xml.i> #include <include/source-interface.xml.i> #include <include/interface/interface-firewall.xml.i> + #include <include/interface/interface-policy.xml.i> <leafNode name="6rd-prefix"> <properties> <help>6rd network prefix</help> diff --git a/interface-definitions/interfaces-vti.xml.in b/interface-definitions/interfaces-vti.xml.in index a8a330f32..f03c7476d 100644 --- a/interface-definitions/interfaces-vti.xml.in +++ b/interface-definitions/interfaces-vti.xml.in @@ -36,6 +36,7 @@ #include <include/interface/mtu-68-16000.xml.i> #include <include/interface/vrf.xml.i> #include <include/interface/interface-firewall.xml.i> + #include <include/interface/interface-policy.xml.i> </children> </tagNode> </children> diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in index c5bb0c8f2..0c13dd4d3 100644 --- a/interface-definitions/interfaces-vxlan.xml.in +++ b/interface-definitions/interfaces-vxlan.xml.in @@ -42,6 +42,7 @@ #include <include/interface/mac.xml.i> #include <include/interface/mtu-1200-16000.xml.i> #include <include/interface/interface-firewall.xml.i> + #include <include/interface/interface-policy.xml.i> <leafNode name="mtu"> <defaultValue>1450</defaultValue> </leafNode> diff --git a/interface-definitions/interfaces-wireguard.xml.in b/interface-definitions/interfaces-wireguard.xml.in index c96b3d46b..7a7c9c1d9 100644 --- a/interface-definitions/interfaces-wireguard.xml.in +++ b/interface-definitions/interfaces-wireguard.xml.in @@ -23,6 +23,7 @@ #include <include/port-number.xml.i> #include <include/interface/mtu-68-16000.xml.i> #include <include/interface/interface-firewall.xml.i> + #include <include/interface/interface-policy.xml.i> <leafNode name="mtu"> <defaultValue>1420</defaultValue> </leafNode> diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in index da739840b..a2d1439a3 100644 --- a/interface-definitions/interfaces-wireless.xml.in +++ b/interface-definitions/interfaces-wireless.xml.in @@ -18,6 +18,7 @@ <children> #include <include/interface/address-ipv4-ipv6-dhcp.xml.i> #include <include/interface/interface-firewall.xml.i> + #include <include/interface/interface-policy.xml.i> <node name="capabilities"> <properties> <help>HT and VHT capabilities for your card</help> diff --git a/interface-definitions/interfaces-wwan.xml.in b/interface-definitions/interfaces-wwan.xml.in index 926c48194..03554feed 100644 --- a/interface-definitions/interfaces-wwan.xml.in +++ b/interface-definitions/interfaces-wwan.xml.in @@ -40,6 +40,7 @@ #include <include/interface/ipv6-options.xml.i> #include <include/interface/dial-on-demand.xml.i> #include <include/interface/interface-firewall.xml.i> + #include <include/interface/interface-policy.xml.i> </children> </tagNode> </children> diff --git a/interface-definitions/policy-route.xml.in b/interface-definitions/policy-route.xml.in new file mode 100644 index 000000000..ed726d1e4 --- /dev/null +++ b/interface-definitions/policy-route.xml.in @@ -0,0 +1,83 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="policy"> + <children> + <tagNode name="ipv6-route" owner="${vyos_conf_scripts_dir}/policy-route.py"> + <properties> + <help>IPv6 policy route rule set name</help> + <priority>201</priority> + </properties> + <children> + #include <include/generic-description.xml.i> + #include <include/firewall/name-default-log.xml.i> + <tagNode name="rule"> + <properties> + <help>Rule number (1-9999)</help> + </properties> + <children> + <node name="destination"> + <properties> + <help>Destination parameters</help> + </properties> + <children> + #include <include/firewall/address-ipv6.xml.i> + #include <include/firewall/source-destination-group-ipv6.xml.i> + #include <include/firewall/port.xml.i> + </children> + </node> + <node name="source"> + <properties> + <help>Source parameters</help> + </properties> + <children> + #include <include/firewall/address-ipv6.xml.i> + #include <include/firewall/source-destination-group-ipv6.xml.i> + #include <include/firewall/port.xml.i> + </children> + </node> + #include <include/policy/route-common-rule-ipv6.xml.i> + </children> + </tagNode> + </children> + </tagNode> + <tagNode name="route" owner="${vyos_conf_scripts_dir}/policy-route.py"> + <properties> + <help>Policy route rule set name</help> + <priority>201</priority> + </properties> + <children> + #include <include/generic-description.xml.i> + #include <include/firewall/name-default-log.xml.i> + <tagNode name="rule"> + <properties> + <help>Rule number (1-9999)</help> + </properties> + <children> + <node name="destination"> + <properties> + <help>Destination parameters</help> + </properties> + <children> + #include <include/firewall/address.xml.i> + #include <include/firewall/source-destination-group.xml.i> + #include <include/firewall/port.xml.i> + </children> + </node> + <node name="source"> + <properties> + <help>Source parameters</help> + </properties> + <children> + #include <include/firewall/address.xml.i> + #include <include/firewall/source-destination-group.xml.i> + #include <include/firewall/port.xml.i> + </children> + </node> + #include <include/policy/route-common-rule.xml.i> + </children> + </tagNode> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index 9b8af7852..8b7402b7e 100644 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -150,8 +150,12 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name): if tcp_flags: output.append(parse_tcp_flags(tcp_flags)) + output.append('counter') + if 'set' in rule_conf: + output.append(parse_policy_set(rule_conf['set'], def_suffix)) + if 'action' in rule_conf: output.append(nft_action(rule_conf['action'])) else: @@ -192,3 +196,22 @@ def parse_time(time): out_days = [f'"{day}"' for day in days if day[0] != '!'] out.append(f'day {{{",".join(out_days)}}}') return " ".join(out) + +def parse_policy_set(set_conf, def_suffix): + out = [] + if 'dscp' in set_conf: + dscp = set_conf['dscp'] + out.append(f'ip{def_suffix} dscp set {dscp}') + if 'mark' in set_conf: + mark = set_conf['mark'] + out.append(f'meta mark set {mark}') + if 'table' in set_conf: + table = set_conf['table'] + if table == 'main': + table = '254' + mark = 0x7FFFFFFF - int(set_conf['table']) + out.append(f'meta mark set {mark}') + if 'tcp_mss' in set_conf: + mss = set_conf['tcp_mss'] + out.append(f'tcp option maxseg size set {mss}') + return " ".join(out) diff --git a/smoketest/scripts/cli/test_policy_route.py b/smoketest/scripts/cli/test_policy_route.py new file mode 100755 index 000000000..70a234187 --- /dev/null +++ b/smoketest/scripts/cli/test_policy_route.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 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 unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.util import cmd + +mark = '100' +table_mark_offset = 0x7fffffff +table_id = '101' + +class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): + def setUp(self): + self.cli_set(['interfaces', 'ethernet', 'eth0', 'address', '172.16.10.1/24']) + self.cli_set(['protocols', 'static', 'table', '101', 'route', '0.0.0.0/0', 'interface', 'eth0']) + + def tearDown(self): + self.cli_delete(['interfaces', 'ethernet', 'eth0']) + self.cli_delete(['policy', 'route']) + self.cli_delete(['policy', 'ipv6-route']) + self.cli_commit() + + def test_pbr_mark(self): + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'source', 'address', '172.16.20.10']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'destination', 'address', '172.16.10.10']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'set', 'mark', mark]) + + self.cli_set(['interfaces', 'ethernet', 'eth0', 'policy', 'route', 'smoketest']) + + self.cli_commit() + + mark_hex = "{0:#010x}".format(int(mark)) + + nftables_search = [ + ['iifname "eth0"', 'jump VYOS_PBR_smoketest'], + ['ip daddr 172.16.10.10', 'ip saddr 172.16.20.10', 'meta mark set ' + mark_hex], + ] + + nftables_output = cmd('sudo nft list table ip mangle') + + for search in nftables_search: + matched = False + for line in nftables_output.split("\n"): + if all(item in line for item in search): + matched = True + break + self.assertTrue(matched) + + def test_pbr_table(self): + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'protocol', 'tcp_udp']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'destination', 'port', '8888']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'set', 'table', table_id]) + + self.cli_set(['interfaces', 'ethernet', 'eth0', 'policy', 'route', 'smoketest']) + + self.cli_commit() + + mark_hex = "{0:#010x}".format(table_mark_offset - int(table_id)) + + nftables_search = [ + ['iifname "eth0"', 'jump VYOS_PBR_smoketest'], + ['meta l4proto { tcp, udp }', 'th dport { 8888 }', 'meta mark set ' + mark_hex] + ] + + nftables_output = cmd('sudo nft list table ip mangle') + + for search in nftables_search: + matched = False + for line in nftables_output.split("\n"): + if all(item in line for item in search): + matched = True + break + self.assertTrue(matched) + + ip_rule_search = [ + ['fwmark ' + hex(table_mark_offset - int(table_id)), 'lookup ' + table_id] + ] + + ip_rule_output = cmd('ip rule show') + + for search in ip_rule_search: + matched = False + for line in ip_rule_output.split("\n"): + if all(item in line for item in search): + matched = True + break + self.assertTrue(matched) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/src/conf_mode/policy-route-interface.py b/src/conf_mode/policy-route-interface.py new file mode 100755 index 000000000..e81135a74 --- /dev/null +++ b/src/conf_mode/policy-route-interface.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 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 re + +from sys import argv +from sys import exit + +from vyos.config import Config +from vyos.ifconfig import Section +from vyos.template import render +from vyos.util import cmd +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + ifname = argv[1] + ifpath = Section.get_config_path(ifname) + if_policy_path = f'interfaces {ifpath} policy' + + if_policy = conf.get_config_dict(if_policy_path, key_mangling=('-', '_'), get_first_key=True, + no_tag_node_value_mangle=True) + + if_policy['ifname'] = ifname + if_policy['policy'] = conf.get_config_dict(['policy'], key_mangling=('-', '_'), get_first_key=True, + no_tag_node_value_mangle=True) + + return if_policy + +def verify(if_policy): + # bail out early - looks like removal from running config + if not if_policy: + return None + + for route in ['route', 'ipv6_route']: + if route in if_policy: + if route not in if_policy['policy']: + raise ConfigError('Policy route not configured') + + route_name = if_policy[route] + + if route_name not in if_policy['policy'][route]: + raise ConfigError(f'Invalid policy route name "{name}"') + + return None + +def generate(if_policy): + return None + +def cleanup_rule(table, chain, ifname, new_name=None): + results = cmd(f'nft -a list chain {table} {chain}').split("\n") + retval = None + for line in results: + if f'oifname "{ifname}"' in line: + if new_name and f'jump {new_name}' in line: + # new_name is used to clear rules for any previously referenced chains + # returns true when rule exists and doesn't need to be created + retval = True + continue + + handle_search = re.search('handle (\d+)', line) + if handle_search: + cmd(f'nft delete rule {table} {chain} handle {handle_search[1]}') + return retval + +def apply(if_policy): + ifname = if_policy['ifname'] + + route_chain = 'VYOS_PBR_PREROUTING' + ipv6_route_chain = 'VYOS_PBR6_PREROUTING' + + if 'route' in if_policy: + name = 'VYOS_PBR_' + if_policy['route'] + rule_exists = cleanup_rule('ip mangle', route_chain, ifname, name) + + if not rule_exists: + cmd(f'nft insert rule ip mangle {route_chain} iifname {ifname} counter jump {name}') + else: + cleanup_rule('ip mangle', route_chain, ifname) + + if 'ipv6_route' in if_policy: + name = 'VYOS_PBR6_' + if_policy['ipv6_route'] + rule_exists = cleanup_rule('ip6 mangle', ipv6_route_chain, ifname, name) + + if not rule_exists: + cmd(f'nft insert rule ip6 mangle {ipv6_route_chain} iifname {ifname} counter jump {name}') + else: + cleanup_rule('ip6 mangle', ipv6_route_chain, ifname) + + 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/policy-route.py b/src/conf_mode/policy-route.py new file mode 100755 index 000000000..d098be68d --- /dev/null +++ b/src/conf_mode/policy-route.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 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 json import loads +from sys import exit + +from vyos.config import Config +from vyos.template import render +from vyos.util import cmd +from vyos.util import dict_search_args +from vyos.util import run +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +mark_offset = 0x7FFFFFFF +nftables_conf = '/run/nftables_policy.conf' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['policy'] + + if not conf.exists(base + ['route']) and not conf.exists(base + ['ipv6-route']): + return None + + policy = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, + no_tag_node_value_mangle=True) + + return policy + +def verify(policy): + # bail out early - looks like removal from running config + if not policy: + return None + + for route in ['route', 'ipv6_route']: + if route in policy: + for name, pol_conf in policy[route].items(): + if 'rule' in pol_conf: + for rule_id, rule_conf in pol_conf.items(): + icmp = 'icmp' if route == 'route' else 'icmpv6' + if icmp in rule_conf: + icmp_defined = False + if 'type_name' in rule_conf[icmp]: + icmp_defined = True + if 'code' in rule_conf[icmp] or 'type' in rule_conf[icmp]: + raise ConfigError(f'{name} rule {rule_id}: Cannot use ICMP type/code with ICMP type-name') + if 'code' in rule_conf[icmp]: + icmp_defined = True + if 'type' not in rule_conf[icmp]: + raise ConfigError(f'{name} rule {rule_id}: ICMP code can only be defined if ICMP type is defined') + if 'type' in rule_conf[icmp]: + icmp_defined = True + + if icmp_defined and 'protocol' not in rule_conf or rule_conf['protocol'] != icmp: + raise ConfigError(f'{name} rule {rule_id}: ICMP type/code or type-name can only be defined if protocol is ICMP') + if 'set' in rule_conf: + if 'tcp_mss' in rule_conf['set']: + tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags') + if not tcp_flags or 'SYN' not in tcp_flags.split(","): + raise ConfigError(f'{name} rule {rule_id}: TCP SYN flag must be set to modify TCP-MSS') + if 'tcp' in rule_conf: + if 'flags' in rule_conf['tcp']: + if 'protocol' not in rule_conf or rule_conf['protocol'] != 'tcp': + raise ConfigError(f'{name} rule {rule_id}: TCP flags can only be set if protocol is set to TCP') + + + return None + +def generate(policy): + if not policy: + if os.path.exists(nftables_conf): + os.unlink(nftables_conf) + return None + + if not os.path.exists(nftables_conf): + policy['first_install'] = True + + render(nftables_conf, 'firewall/nftables-policy.tmpl', policy) + return None + +def apply_table_marks(policy): + for route in ['route', 'ipv6_route']: + if route in policy: + for name, pol_conf in policy[route].items(): + if 'rule' in pol_conf: + for rule_id, rule_conf in pol_conf['rule'].items(): + set_table = dict_search_args(rule_conf, 'set', 'table') + if set_table: + if set_table == 'main': + set_table = '254' + table_mark = mark_offset - int(set_table) + cmd(f'ip rule add fwmark {table_mark} table {set_table}') + +def cleanup_table_marks(): + json_rules = cmd('ip -j -N rule list') + rules = loads(json_rules) + for rule in rules: + if 'fwmark' not in rule or 'table' not in rule: + continue + fwmark = rule['fwmark'] + table = int(rule['table']) + if fwmark[:2] == '0x': + fwmark = int(fwmark, 16) + if (int(fwmark) == (mark_offset - table)): + cmd(f'ip rule del fwmark {fwmark} table {table}') + +def apply(policy): + if not policy or 'first_install' not in policy: + run(f'nft flush table ip mangle') + run(f'nft flush table ip6 mangle') + + if not policy: + cleanup_table_marks() + return None + + install_result = run(f'nft -f {nftables_conf}') + if install_result == 1: + raise ConfigError('Failed to apply policy based routing') + + if 'first_install' not in policy: + cleanup_table_marks() + + apply_table_marks(policy) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) |