diff --git a/data/templates/conntrack/sysctl.conf.j2 b/data/templates/conntrack/sysctl.conf.j2 index 986f75c61..554512f4d 100644 --- a/data/templates/conntrack/sysctl.conf.j2 +++ b/data/templates/conntrack/sysctl.conf.j2 @@ -1,27 +1,9 @@ # Autogenerated by system_conntrack.py {# all values have defaults - thus no checking required #} net.netfilter.nf_conntrack_expect_max = {{ expect_table_size }} net.netfilter.nf_conntrack_max = {{ table_size }} - net.ipv4.tcp_max_syn_backlog = {{ tcp.half_open_connections }} - net.netfilter.nf_conntrack_tcp_loose = {{ '1' if tcp.loose is vyos_defined('enable') else '0' }} net.netfilter.nf_conntrack_tcp_max_retrans = {{ tcp.max_retrans }} - -net.netfilter.nf_conntrack_icmp_timeout = {{ timeout.icmp }} -net.netfilter.nf_conntrack_generic_timeout = {{ timeout.other }} - -net.netfilter.nf_conntrack_tcp_timeout_close_wait = {{ timeout.tcp.close_wait }} -net.netfilter.nf_conntrack_tcp_timeout_close = {{ timeout.tcp.close }} -net.netfilter.nf_conntrack_tcp_timeout_established = {{ timeout.tcp.established }} -net.netfilter.nf_conntrack_tcp_timeout_fin_wait = {{ timeout.tcp.fin_wait }} -net.netfilter.nf_conntrack_tcp_timeout_last_ack = {{ timeout.tcp.last_ack }} -net.netfilter.nf_conntrack_tcp_timeout_syn_recv = {{ timeout.tcp.syn_recv }} -net.netfilter.nf_conntrack_tcp_timeout_syn_sent = {{ timeout.tcp.syn_sent }} -net.netfilter.nf_conntrack_tcp_timeout_time_wait = {{ timeout.tcp.time_wait }} - -net.netfilter.nf_conntrack_udp_timeout = {{ timeout.udp.other }} -net.netfilter.nf_conntrack_udp_timeout_stream = {{ timeout.udp.stream }} - -net.netfilter.nf_conntrack_acct = {{ '1' if flow_accounting is vyos_defined else '0' }} +net.netfilter.nf_conntrack_acct = {{ '1' if flow_accounting is vyos_defined else '0' }} \ No newline at end of file diff --git a/data/templates/firewall/sysctl-firewall.conf.j2 b/data/templates/firewall/sysctl-firewall.conf.j2 new file mode 100644 index 000000000..b9c3311e2 --- /dev/null +++ b/data/templates/firewall/sysctl-firewall.conf.j2 @@ -0,0 +1,28 @@ +# Autogenerated by firewall.py + +# gloabl options +net.ipv4.icmp_echo_ignore_all = {{ 0 if global_options.all_ping == 'enable' else 1 }} +net.ipv4.icmp_echo_ignore_broadcasts = {{ 0 if global_options.broadcast_ping == 'enable' else 1 }} +net.ipv4.conf.all.bc_forwarding = {{ 1 if global_options.directed_broadcast == 'enable' else 0 }} +net.ipv4.conf.*.accept_source_route = {{ 1 if global_options.ip_src_route == 'enable' else 0 }} +net.ipv6.conf.*.accept_redirects = {{ 1 if global_options.ipv6_receive_redirects == 'enable' else 0 }} +net.ipv6.conf.*.accept_source_route = {{ 0 if global_options.ipv6_src_route == 'enable' else -1 }} +net.ipv4.conf.all.log_martians = {{ 1 if global_options.log_martians == 'enable' else 0 }} +net.ipv4.conf.*.accept_redirects = {{ 1 if global_options.receive_redirects == 'enable' else 0 }} +net.ipv4.conf.*.send_redirects = {{ 1 if global_options.send_redirects == 'enable' else 0 }} +net.ipv4.tcp_syncookies = {{ 1 if global_options.syn_cookies == 'enable' else 0 }} +net.ipv4.tcp_rfc1337 = {{ 1 if global_options.twa_hazards_protection == 'enable' else 0 }} + +## Timeout values: +net.netfilter.nf_conntrack_icmp_timeout = {{ global_options.timeout.icmp }} +net.netfilter.nf_conntrack_generic_timeout = {{ global_options.timeout.other }} +net.netfilter.nf_conntrack_tcp_timeout_close_wait = {{ global_options.timeout.tcp.close_wait }} +net.netfilter.nf_conntrack_tcp_timeout_close = {{ global_options.timeout.tcp.close }} +net.netfilter.nf_conntrack_tcp_timeout_established = {{ global_options.timeout.tcp.established }} +net.netfilter.nf_conntrack_tcp_timeout_fin_wait = {{ global_options.timeout.tcp.fin_wait }} +net.netfilter.nf_conntrack_tcp_timeout_last_ack = {{ global_options.timeout.tcp.last_ack }} +net.netfilter.nf_conntrack_tcp_timeout_syn_recv = {{ global_options.timeout.tcp.syn_recv }} +net.netfilter.nf_conntrack_tcp_timeout_syn_sent = {{ global_options.timeout.tcp.syn_sent }} +net.netfilter.nf_conntrack_tcp_timeout_time_wait = {{ global_options.timeout.tcp.time_wait }} +net.netfilter.nf_conntrack_udp_timeout = {{ global_options.timeout.udp.other }} +net.netfilter.nf_conntrack_udp_timeout_stream = {{ global_options.timeout.udp.stream }} diff --git a/interface-definitions/include/firewall/action-and-notrack.xml.i b/interface-definitions/include/firewall/action-and-notrack.xml.i index e063c58d5..de11f7dd5 100644 --- a/interface-definitions/include/firewall/action-and-notrack.xml.i +++ b/interface-definitions/include/firewall/action-and-notrack.xml.i @@ -1,45 +1,45 @@ <!-- include start from firewall/action-and-notrack.xml.i --> <leafNode name="action"> <properties> <help>Rule action</help> <completionHelp> <list>accept continue jump notrack reject return drop queue</list> </completionHelp> <valueHelp> <format>accept</format> <description>Accept matching entries</description> </valueHelp> <valueHelp> <format>continue</format> <description>Continue parsing next rule</description> </valueHelp> <valueHelp> <format>jump</format> <description>Jump to another chain</description> </valueHelp> <valueHelp> <format>reject</format> <description>Reject matching entries</description> </valueHelp> <valueHelp> <format>return</format> <description>Return from the current chain and continue at the next rule of the last chain</description> </valueHelp> <valueHelp> <format>drop</format> <description>Drop matching entries</description> </valueHelp> <valueHelp> <format>queue</format> <description>Enqueue packet to userspace</description> </valueHelp> <valueHelp> <format>notrack</format> - <description>Igone connection tracking</description> + <description>Ignore connection tracking</description> </valueHelp> <constraint> <regex>(accept|continue|jump|notrack|reject|return|drop|queue)</regex> </constraint> </properties> </leafNode> <!-- include end --> diff --git a/interface-definitions/include/firewall/global-options.xml.i b/interface-definitions/include/firewall/global-options.xml.i index 9cd0b3239..9039b76fd 100644 --- a/interface-definitions/include/firewall/global-options.xml.i +++ b/interface-definitions/include/firewall/global-options.xml.i @@ -1,333 +1,341 @@ <!-- include start from firewall/global-options.xml.i --> <node name="global-options"> <properties> <help>Global Options</help> </properties> <children> <leafNode name="all-ping"> <properties> <help>Policy for handling of all IPv4 ICMP echo requests</help> <completionHelp> <list>enable disable</list> </completionHelp> <valueHelp> <format>enable</format> <description>Enable processing of all IPv4 ICMP echo requests</description> </valueHelp> <valueHelp> <format>disable</format> <description>Disable processing of all IPv4 ICMP echo requests</description> </valueHelp> <constraint> <regex>(enable|disable)</regex> </constraint> </properties> <defaultValue>enable</defaultValue> </leafNode> <leafNode name="broadcast-ping"> <properties> <help>Policy for handling broadcast IPv4 ICMP echo and timestamp requests</help> <completionHelp> <list>enable disable</list> </completionHelp> <valueHelp> <format>enable</format> <description>Enable processing of broadcast IPv4 ICMP echo/timestamp requests</description> </valueHelp> <valueHelp> <format>disable</format> <description>Disable processing of broadcast IPv4 ICMP echo/timestamp requests</description> </valueHelp> <constraint> <regex>(enable|disable)</regex> </constraint> </properties> <defaultValue>disable</defaultValue> </leafNode> <leafNode name="directed-broadcast"> <properties> <help>Policy for handling IPv4 directed broadcast forwarding on all interfaces</help> <completionHelp> <list>enable disable</list> </completionHelp> <valueHelp> <format>enable</format> <description>Enable IPv4 directed broadcast forwarding on all interfaces</description> </valueHelp> <valueHelp> <format>disable</format> <description>Disable IPv4 directed broadcast forwarding on all interfaces</description> </valueHelp> <constraint> <regex>(enable|disable)</regex> </constraint> </properties> <defaultValue>enable</defaultValue> </leafNode> <leafNode name="ip-src-route"> <properties> <help>Policy for handling IPv4 packets with source route option</help> <completionHelp> <list>enable disable</list> </completionHelp> <valueHelp> <format>enable</format> <description>Enable processing of IPv4 packets with source route option</description> </valueHelp> <valueHelp> <format>disable</format> <description>Disable processing of IPv4 packets with source route option</description> </valueHelp> <constraint> <regex>(enable|disable)</regex> </constraint> </properties> <defaultValue>disable</defaultValue> </leafNode> <leafNode name="log-martians"> <properties> <help>Policy for logging IPv4 packets with invalid addresses</help> <completionHelp> <list>enable disable</list> </completionHelp> <valueHelp> <format>enable</format> <description>Enable logging of IPv4 packets with invalid addresses</description> </valueHelp> <valueHelp> <format>disable</format> <description>Disable logging of Ipv4 packets with invalid addresses</description> </valueHelp> <constraint> <regex>(enable|disable)</regex> </constraint> </properties> <defaultValue>enable</defaultValue> </leafNode> <leafNode name="receive-redirects"> <properties> <help>Policy for handling received IPv4 ICMP redirect messages</help> <completionHelp> <list>enable disable</list> </completionHelp> <valueHelp> <format>enable</format> <description>Enable processing of received IPv4 ICMP redirect messages</description> </valueHelp> <valueHelp> <format>disable</format> <description>Disable processing of received IPv4 ICMP redirect messages</description> </valueHelp> <constraint> <regex>(enable|disable)</regex> </constraint> </properties> <defaultValue>disable</defaultValue> </leafNode> <leafNode name="resolver-cache"> <properties> <help>Retains last successful value if domain resolution fails</help> <valueless/> </properties> </leafNode> <leafNode name="resolver-interval"> <properties> <help>Domain resolver update interval</help> <valueHelp> <format>u32:10-3600</format> <description>Interval (seconds)</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 10-3600"/> </constraint> </properties> <defaultValue>300</defaultValue> </leafNode> <leafNode name="send-redirects"> <properties> <help>Policy for sending IPv4 ICMP redirect messages</help> <completionHelp> <list>enable disable</list> </completionHelp> <valueHelp> <format>enable</format> <description>Enable sending IPv4 ICMP redirect messages</description> </valueHelp> <valueHelp> <format>disable</format> <description>Disable sending IPv4 ICMP redirect messages</description> </valueHelp> <constraint> <regex>(enable|disable)</regex> </constraint> </properties> <defaultValue>enable</defaultValue> </leafNode> <leafNode name="source-validation"> <properties> <help>Policy for IPv4 source validation by reversed path, as specified in RFC3704</help> <completionHelp> <list>strict loose disable</list> </completionHelp> <valueHelp> <format>strict</format> <description>Enable IPv4 Strict Reverse Path Forwarding as defined in RFC3704</description> </valueHelp> <valueHelp> <format>loose</format> <description>Enable IPv4 Loose Reverse Path Forwarding as defined in RFC3704</description> </valueHelp> <valueHelp> <format>disable</format> <description>No IPv4 source validation</description> </valueHelp> <constraint> <regex>(strict|loose|disable)</regex> </constraint> </properties> <defaultValue>disable</defaultValue> </leafNode> <node name="state-policy"> <properties> <help>Global firewall state-policy</help> </properties> <children> <node name="established"> <properties> <help>Global firewall policy for packets part of an established connection</help> </properties> <children> #include <include/firewall/action-accept-drop-reject.xml.i> #include <include/firewall/log.xml.i> #include <include/firewall/rule-log-level.xml.i> </children> </node> <node name="invalid"> <properties> <help>Global firewall policy for packets part of an invalid connection</help> </properties> <children> #include <include/firewall/action-accept-drop-reject.xml.i> #include <include/firewall/log.xml.i> #include <include/firewall/rule-log-level.xml.i> </children> </node> <node name="related"> <properties> <help>Global firewall policy for packets part of a related connection</help> </properties> <children> #include <include/firewall/action-accept-drop-reject.xml.i> #include <include/firewall/log.xml.i> #include <include/firewall/rule-log-level.xml.i> </children> </node> </children> </node> <leafNode name="syn-cookies"> <properties> <help>Policy for using TCP SYN cookies with IPv4</help> <completionHelp> <list>enable disable</list> </completionHelp> <valueHelp> <format>enable</format> <description>Enable use of TCP SYN cookies with IPv4</description> </valueHelp> <valueHelp> <format>disable</format> <description>Disable use of TCP SYN cookies with IPv4</description> </valueHelp> <constraint> <regex>(enable|disable)</regex> </constraint> </properties> <defaultValue>enable</defaultValue> </leafNode> + <node name="timeout"> + <properties> + <help>Connection timeout options</help> + </properties> + <children> + #include <include/firewall/timeout-common-protocols.xml.i> + </children> + </node> <leafNode name="twa-hazards-protection"> <properties> <help>RFC1337 TCP TIME-WAIT assasination hazards protection</help> <completionHelp> <list>enable disable</list> </completionHelp> <valueHelp> <format>enable</format> <description>Enable RFC1337 TIME-WAIT hazards protection</description> </valueHelp> <valueHelp> <format>disable</format> <description>Disable RFC1337 TIME-WAIT hazards protection</description> </valueHelp> <constraint> <regex>(enable|disable)</regex> </constraint> </properties> <defaultValue>disable</defaultValue> </leafNode> <leafNode name="ipv6-receive-redirects"> <properties> <help>Policy for handling received ICMPv6 redirect messages</help> <completionHelp> <list>enable disable</list> </completionHelp> <valueHelp> <format>enable</format> <description>Enable processing of received ICMPv6 redirect messages</description> </valueHelp> <valueHelp> <format>disable</format> <description>Disable processing of received ICMPv6 redirect messages</description> </valueHelp> <constraint> <regex>(enable|disable)</regex> </constraint> </properties> <defaultValue>disable</defaultValue> </leafNode> <leafNode name="ipv6-source-validation"> <properties> <help>Policy for IPv6 source validation by reversed path, as specified in RFC3704</help> <completionHelp> <list>strict loose disable</list> </completionHelp> <valueHelp> <format>strict</format> <description>Enable IPv6 Strict Reverse Path Forwarding as defined in RFC3704</description> </valueHelp> <valueHelp> <format>loose</format> <description>Enable IPv6 Loose Reverse Path Forwarding as defined in RFC3704</description> </valueHelp> <valueHelp> <format>disable</format> <description>No IPv6 source validation</description> </valueHelp> <constraint> <regex>(strict|loose|disable)</regex> </constraint> </properties> <defaultValue>disable</defaultValue> </leafNode> <leafNode name="ipv6-src-route"> <properties> <help>Policy for handling IPv6 packets with routing extension header</help> <completionHelp> <list>enable disable</list> </completionHelp> <valueHelp> <format>enable</format> <description>Enable processing of IPv6 packets with routing header type 2</description> </valueHelp> <valueHelp> <format>disable</format> <description>Disable processing of IPv6 packets with routing header</description> </valueHelp> <constraint> <regex>(enable|disable)</regex> </constraint> </properties> <defaultValue>disable</defaultValue> </leafNode> </children> </node> <!-- include end --> diff --git a/interface-definitions/include/conntrack/timeout-common-protocols.xml.i b/interface-definitions/include/firewall/timeout-common-protocols.xml.i similarity index 98% rename from interface-definitions/include/conntrack/timeout-common-protocols.xml.i rename to interface-definitions/include/firewall/timeout-common-protocols.xml.i index 2676d846e..037d7d2b1 100644 --- a/interface-definitions/include/conntrack/timeout-common-protocols.xml.i +++ b/interface-definitions/include/firewall/timeout-common-protocols.xml.i @@ -1,172 +1,171 @@ -<!-- include start from conntrack/timeout-common-protocols.xml.i --> +<!-- include start from firewall/timeout-common-protocols.xml.i --> <leafNode name="icmp"> <properties> <help>ICMP timeout in seconds</help> <valueHelp> <format>u32:1-21474836</format> <description>ICMP timeout in seconds</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-21474836"/> </constraint> </properties> <defaultValue>30</defaultValue> </leafNode> <leafNode name="other"> <properties> <help>Generic connection timeout in seconds</help> <valueHelp> <format>u32:1-21474836</format> <description>Generic connection timeout in seconds</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-21474836"/> </constraint> </properties> <defaultValue>600</defaultValue> </leafNode> <node name="tcp"> <properties> <help>TCP connection timeout options</help> </properties> <children> <leafNode name="close-wait"> <properties> <help>TCP CLOSE-WAIT timeout in seconds</help> <valueHelp> <format>u32:1-21474836</format> <description>TCP CLOSE-WAIT timeout in seconds</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-21474836"/> </constraint> </properties> <defaultValue>60</defaultValue> </leafNode> <leafNode name="close"> <properties> <help>TCP CLOSE timeout in seconds</help> <valueHelp> <format>u32:1-21474836</format> <description>TCP CLOSE timeout in seconds</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-21474836"/> </constraint> </properties> <defaultValue>10</defaultValue> </leafNode> <leafNode name="established"> <properties> <help>TCP ESTABLISHED timeout in seconds</help> <valueHelp> <format>u32:1-21474836</format> <description>TCP ESTABLISHED timeout in seconds</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-21474836"/> </constraint> </properties> <defaultValue>432000</defaultValue> </leafNode> <leafNode name="fin-wait"> <properties> <help>TCP FIN-WAIT timeout in seconds</help> <valueHelp> <format>u32:1-21474836</format> <description>TCP FIN-WAIT timeout in seconds</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-21474836"/> </constraint> </properties> <defaultValue>120</defaultValue> </leafNode> <leafNode name="last-ack"> <properties> <help>TCP LAST-ACK timeout in seconds</help> <valueHelp> <format>u32:1-21474836</format> <description>TCP LAST-ACK timeout in seconds</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-21474836"/> </constraint> </properties> <defaultValue>30</defaultValue> </leafNode> <leafNode name="syn-recv"> <properties> <help>TCP SYN-RECEIVED timeout in seconds</help> <valueHelp> <format>u32:1-21474836</format> <description>TCP SYN-RECEIVED timeout in seconds</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-21474836"/> </constraint> </properties> <defaultValue>60</defaultValue> </leafNode> <leafNode name="syn-sent"> <properties> <help>TCP SYN-SENT timeout in seconds</help> <valueHelp> <format>u32:1-21474836</format> <description>TCP SYN-SENT timeout in seconds</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-21474836"/> </constraint> </properties> <defaultValue>120</defaultValue> </leafNode> <leafNode name="time-wait"> <properties> <help>TCP TIME-WAIT timeout in seconds</help> <valueHelp> <format>u32:1-21474836</format> <description>TCP TIME-WAIT timeout in seconds</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-21474836"/> </constraint> </properties> <defaultValue>120</defaultValue> </leafNode> </children> </node> <node name="udp"> <properties> <help>UDP timeout options</help> </properties> <children> <leafNode name="other"> <properties> <help>UDP generic timeout in seconds</help> <valueHelp> <format>u32:1-21474836</format> <description>UDP generic timeout in seconds</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-21474836"/> </constraint> </properties> <defaultValue>30</defaultValue> </leafNode> <leafNode name="stream"> <properties> <help>UDP stream timeout in seconds</help> <valueHelp> <format>u32:1-21474836</format> <description>UDP stream timeout in seconds</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-21474836"/> </constraint> </properties> <defaultValue>180</defaultValue> </leafNode> </children> </node> -<!-- include end --> diff --git a/interface-definitions/include/version/firewall-version.xml.i b/interface-definitions/include/version/firewall-version.xml.i index fa8e26f78..560ed9e5f 100644 --- a/interface-definitions/include/version/firewall-version.xml.i +++ b/interface-definitions/include/version/firewall-version.xml.i @@ -1,3 +1,3 @@ <!-- include start from include/version/firewall-version.xml.i --> -<syntaxVersion component='firewall' version='15'></syntaxVersion> +<syntaxVersion component='firewall' version='16'></syntaxVersion> <!-- include end --> diff --git a/interface-definitions/system_conntrack.xml.in b/interface-definitions/system_conntrack.xml.in index 219c6e28e..33aa832a8 100644 --- a/interface-definitions/system_conntrack.xml.in +++ b/interface-definitions/system_conntrack.xml.in @@ -1,519 +1,518 @@ <?xml version="1.0"?> <interfaceDefinition> <node name="system"> <children> <node name="conntrack" owner="${vyos_conf_scripts_dir}/system_conntrack.py"> <properties> <help>Connection Tracking Engine Options</help> <!-- Before NAT and conntrack-sync are configured --> <priority>218</priority> </properties> <children> <leafNode name="flow-accounting"> <properties> <help>Enable connection tracking flow accounting</help> <valueless/> </properties> </leafNode> <leafNode name="expect-table-size"> <properties> <help>Size of connection tracking expect table</help> <valueHelp> <format>u32:1-50000000</format> <description>Number of entries allowed in connection tracking expect table</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-50000000"/> </constraint> </properties> <defaultValue>2048</defaultValue> </leafNode> <leafNode name="hash-size"> <properties> <help>Hash size for connection tracking table</help> <valueHelp> <format>u32:1-50000000</format> <description>Size of hash to use for connection tracking table</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-50000000"/> </constraint> </properties> <defaultValue>32768</defaultValue> </leafNode> <node name="ignore"> <properties> <help>Customized rules to ignore selective connection tracking</help> </properties> <children> <node name="ipv4"> <properties> <help>IPv4 rules</help> </properties> <children> <tagNode name="rule"> <properties> <help>Rule number</help> <valueHelp> <format>u32:1-999999</format> <description>Number of conntrack ignore rule</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-999999"/> </constraint> <constraintErrorMessage>Ignore rule number must be between 1 and 999999</constraintErrorMessage> </properties> <children> #include <include/generic-description.xml.i> <node name="destination"> <properties> <help>Destination parameters</help> </properties> <children> #include <include/firewall/source-destination-group-ipv4.xml.i> #include <include/nat-address.xml.i> #include <include/nat-port.xml.i> </children> </node> <leafNode name="inbound-interface"> <properties> <help>Interface to ignore connections tracking on</help> <completionHelp> <list>any</list> <script>${vyos_completion_dir}/list_interfaces</script> </completionHelp> </properties> </leafNode> #include <include/ip-protocol.xml.i> <leafNode name="protocol"> <properties> <help>Protocol to match (protocol name, number, or "all")</help> <completionHelp> <script>${vyos_completion_dir}/list_protocols.sh</script> <list>all tcp_udp</list> </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>u32:0-255</format> <description>IP protocol number</description> </valueHelp> <valueHelp> <format><protocol></format> <description>IP protocol name</description> </valueHelp> <valueHelp> <format>!<protocol></format> <description>IP protocol name</description> </valueHelp> <constraint> <validator name="ip-protocol"/> </constraint> </properties> </leafNode> <node name="source"> <properties> <help>Source parameters</help> </properties> <children> #include <include/firewall/source-destination-group-ipv4.xml.i> #include <include/nat-address.xml.i> #include <include/nat-port.xml.i> </children> </node> #include <include/firewall/tcp-flags.xml.i> </children> </tagNode> </children> </node> <node name="ipv6"> <properties> <help>IPv6 rules</help> </properties> <children> <tagNode name="rule"> <properties> <help>Rule number</help> <valueHelp> <format>u32:1-999999</format> <description>Number of conntrack ignore rule</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-999999"/> </constraint> <constraintErrorMessage>Ignore rule number must be between 1 and 999999</constraintErrorMessage> </properties> <children> #include <include/generic-description.xml.i> <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/nat-port.xml.i> </children> </node> <leafNode name="inbound-interface"> <properties> <help>Interface to ignore connections tracking on</help> <completionHelp> <list>any</list> <script>${vyos_completion_dir}/list_interfaces</script> </completionHelp> </properties> </leafNode> #include <include/ip-protocol.xml.i> <leafNode name="protocol"> <properties> <help>Protocol to match (protocol name, number, or "all")</help> <completionHelp> <script>${vyos_completion_dir}/list_protocols.sh</script> <list>all tcp_udp</list> </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>u32:0-255</format> <description>IP protocol number</description> </valueHelp> <valueHelp> <format><protocol></format> <description>IP protocol name</description> </valueHelp> <valueHelp> <format>!<protocol></format> <description>IP protocol name</description> </valueHelp> <constraint> <validator name="ip-protocol"/> </constraint> </properties> </leafNode> <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/nat-port.xml.i> </children> </node> #include <include/firewall/tcp-flags.xml.i> </children> </tagNode> </children> </node> </children> </node> <node name="log"> <properties> <help>Log connection tracking events per protocol</help> </properties> <children> <node name="icmp"> <properties> <help>Log connection tracking events for ICMP</help> </properties> <children> #include <include/conntrack/log-common.xml.i> </children> </node> <node name="other"> <properties> <help>Log connection tracking events for all protocols other than TCP, UDP and ICMP</help> </properties> <children> #include <include/conntrack/log-common.xml.i> </children> </node> <node name="tcp"> <properties> <help>Log connection tracking events for TCP</help> </properties> <children> #include <include/conntrack/log-common.xml.i> </children> </node> <node name="udp"> <properties> <help>Log connection tracking events for UDP</help> </properties> <children> #include <include/conntrack/log-common.xml.i> </children> </node> </children> </node> <node name="modules"> <properties> <help>Connection tracking modules</help> </properties> <children> <leafNode name="ftp"> <properties> <help>FTP connection tracking</help> <valueless/> </properties> </leafNode> <leafNode name="h323"> <properties> <help>H.323 connection tracking</help> <valueless/> </properties> </leafNode> <leafNode name="nfs"> <properties> <help>NFS connection tracking</help> <valueless/> </properties> </leafNode> <leafNode name="pptp"> <properties> <help>PPTP connection tracking</help> <valueless/> </properties> </leafNode> <leafNode name="rtsp"> <properties> <help>RTSP connection tracking</help> <valueless/> </properties> </leafNode> <leafNode name="sip"> <properties> <help>SIP connection tracking</help> <valueless/> </properties> </leafNode> <leafNode name="sqlnet"> <properties> <help>SQLnet connection tracking</help> <valueless/> </properties> </leafNode> <leafNode name="tftp"> <properties> <help>TFTP connection tracking</help> <valueless/> </properties> </leafNode> </children> </node> <leafNode name="table-size"> <properties> <help>Size of connection tracking table</help> <valueHelp> <format>u32:1-50000000</format> <description>Number of entries allowed in connection tracking table</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-50000000"/> </constraint> </properties> <defaultValue>262144</defaultValue> </leafNode> <node name="tcp"> <properties> <help>TCP options</help> </properties> <children> <leafNode name="half-open-connections"> <properties> <help>Maximum number of TCP half-open connections</help> <valueHelp> <format>u32:1-2147483647</format> <description>Generic connection timeout in seconds</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-2147483647"/> </constraint> </properties> <defaultValue>512</defaultValue> </leafNode> <leafNode name="loose"> <properties> <help>Policy to track previously established connections</help> <completionHelp> <list>enable disable</list> </completionHelp> <valueHelp> <format>enable</format> <description>Allow tracking of previously established connections</description> </valueHelp> <valueHelp> <format>disable</format> <description>Do not allow tracking of previously established connections</description> </valueHelp> <constraint> <regex>(enable|disable)</regex> </constraint> </properties> <defaultValue>enable</defaultValue> </leafNode> <leafNode name="max-retrans"> <properties> <help>Maximum number of packets that can be retransmitted without received an ACK</help> <valueHelp> <format>u32:1-255</format> <description>Number of packets to be retransmitted</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-255"/> </constraint> </properties> <defaultValue>3</defaultValue> </leafNode> </children> </node> <node name="timeout"> <properties> <help>Connection timeout options</help> </properties> <children> <node name="custom"> <properties> <help>Define custom timeouts per connection</help> </properties> <children> <node name="ipv4"> <properties> <help>IPv4 rules</help> </properties> <children> <tagNode name="rule"> <properties> <help>Rule number</help> <valueHelp> <format>u32:1-999999</format> <description>Number of conntrack rule</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-999999"/> </constraint> <constraintErrorMessage>Ignore rule number must be between 1 and 999999</constraintErrorMessage> </properties> <children> #include <include/generic-description.xml.i> <node name="destination"> <properties> <help>Destination parameters</help> </properties> <children> #include <include/nat-address.xml.i> #include <include/nat-port.xml.i> </children> </node> <leafNode name="inbound-interface"> <properties> <help>Interface to ignore connections tracking on</help> <completionHelp> <list>any</list> <script>${vyos_completion_dir}/list_interfaces</script> </completionHelp> </properties> </leafNode> <node name="protocol"> <properties> <help>Customize protocol specific timers, one protocol configuration per rule</help> </properties> <children> #include <include/conntrack/timeout-custom-protocols.xml.i> </children> </node> <node name="source"> <properties> <help>Source parameters</help> </properties> <children> #include <include/nat-address.xml.i> #include <include/nat-port.xml.i> </children> </node> </children> </tagNode> </children> </node> <node name="ipv6"> <properties> <help>IPv6 rules</help> </properties> <children> <tagNode name="rule"> <properties> <help>Rule number</help> <valueHelp> <format>u32:1-999999</format> <description>Number of conntrack rule</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-999999"/> </constraint> <constraintErrorMessage>Ignore rule number must be between 1 and 999999</constraintErrorMessage> </properties> <children> #include <include/generic-description.xml.i> <node name="destination"> <properties> <help>Destination parameters</help> </properties> <children> #include <include/firewall/address-ipv6.xml.i> #include <include/nat-port.xml.i> </children> </node> <leafNode name="inbound-interface"> <properties> <help>Interface to ignore connections tracking on</help> <completionHelp> <list>any</list> <script>${vyos_completion_dir}/list_interfaces</script> </completionHelp> </properties> </leafNode> <node name="protocol"> <properties> <help>Customize protocol specific timers, one protocol configuration per rule</help> </properties> <children> #include <include/conntrack/timeout-custom-protocols.xml.i> </children> </node> <node name="source"> <properties> <help>Source parameters</help> </properties> <children> #include <include/firewall/address-ipv6.xml.i> #include <include/nat-port.xml.i> </children> </node> </children> </tagNode> </children> </node> </children> </node> - #include <include/conntrack/timeout-common-protocols.xml.i> </children> </node> </children> </node> </children> </node> </interfaceDefinition> diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py index 5cf2b2146..0943d8e24 100755 --- a/smoketest/scripts/cli/test_firewall.py +++ b/smoketest/scripts/cli/test_firewall.py @@ -1,911 +1,999 @@ #!/usr/bin/env python3 # # Copyright (C) 2021-2023 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 glob import glob from time import sleep from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError from vyos.utils.process import run +from vyos.utils.file import read_file sysfs_config = { 'all_ping': {'sysfs': '/proc/sys/net/ipv4/icmp_echo_ignore_all', 'default': '0', 'test_value': 'disable'}, 'broadcast_ping': {'sysfs': '/proc/sys/net/ipv4/icmp_echo_ignore_broadcasts', 'default': '1', 'test_value': 'enable'}, 'directed_broadcast': {'sysfs': '/proc/sys/net/ipv4/conf/all/bc_forwarding', 'default': '1', 'test_value': 'disable'}, 'ip_src_route': {'sysfs': '/proc/sys/net/ipv4/conf/*/accept_source_route', 'default': '0', 'test_value': 'enable'}, 'ipv6_receive_redirects': {'sysfs': '/proc/sys/net/ipv6/conf/*/accept_redirects', 'default': '0', 'test_value': 'enable'}, 'ipv6_src_route': {'sysfs': '/proc/sys/net/ipv6/conf/*/accept_source_route', 'default': '-1', 'test_value': 'enable'}, 'log_martians': {'sysfs': '/proc/sys/net/ipv4/conf/all/log_martians', 'default': '1', 'test_value': 'disable'}, 'receive_redirects': {'sysfs': '/proc/sys/net/ipv4/conf/*/accept_redirects', 'default': '0', 'test_value': 'enable'}, 'send_redirects': {'sysfs': '/proc/sys/net/ipv4/conf/*/send_redirects', 'default': '1', 'test_value': 'disable'}, 'syn_cookies': {'sysfs': '/proc/sys/net/ipv4/tcp_syncookies', 'default': '1', 'test_value': 'disable'}, 'twa_hazards_protection': {'sysfs': '/proc/sys/net/ipv4/tcp_rfc1337', 'default': '0', 'test_value': 'enable'} } +def get_sysctl(parameter): + tmp = parameter.replace(r'.', r'/') + return read_file(f'/proc/sys/{tmp}') + class TestFirewall(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): super(TestFirewall, cls).setUpClass() # ensure we can also run this test on a live system - so lets clean # out the current configuration :) cls.cli_delete(cls, ['firewall']) @classmethod def tearDownClass(cls): super(TestFirewall, cls).tearDownClass() def tearDown(self): self.cli_delete(['firewall']) self.cli_commit() # Verify chains/sets are cleaned up from nftables nftables_search = [ ['set M_smoketest_mac'], ['set N_smoketest_network'], ['set P_smoketest_port'], ['set D_smoketest_domain'], ['set RECENT_smoketest_4'], ['chain NAME_smoketest'] ] self.verify_nftables(nftables_search, 'ip vyos_filter', inverse=True) def wait_for_domain_resolver(self, table, set_name, element, max_wait=10): # Resolver no longer blocks commit, need to wait for daemon to populate set count = 0 while count < max_wait: code = run(f'sudo nft get element {table} {set_name} {{ {element} }}') if code == 0: return True count += 1 sleep(1) return False def test_geoip(self): self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'action', 'drop']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'source', 'geoip', 'country-code', 'se']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'source', 'geoip', 'country-code', 'gb']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '2', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '2', 'source', 'geoip', 'country-code', 'de']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '2', 'source', 'geoip', 'country-code', 'fr']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '2', 'source', 'geoip', 'inverse-match']) self.cli_commit() nftables_search = [ ['ip saddr @GEOIP_CC_name_smoketest_1', 'drop'], ['ip saddr != @GEOIP_CC_name_smoketest_2', 'accept'] ] # -t prevents 1000+ GeoIP elements being returned self.verify_nftables(nftables_search, 'ip vyos_filter', args='-t') def test_groups(self): hostmap_path = ['system', 'static-host-mapping', 'host-name'] example_org = ['192.0.2.8', '192.0.2.10', '192.0.2.11'] self.cli_set(hostmap_path + ['example.com', 'inet', '192.0.2.5']) for ips in example_org: self.cli_set(hostmap_path + ['example.org', 'inet', ips]) self.cli_commit() self.cli_set(['firewall', 'group', 'mac-group', 'smoketest_mac', 'mac-address', '00:01:02:03:04:05']) self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network', 'network', '172.16.99.0/24']) self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port', 'port', '53']) self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port', 'port', '123']) self.cli_set(['firewall', 'group', 'domain-group', 'smoketest_domain', 'address', 'example.com']) self.cli_set(['firewall', 'group', 'domain-group', 'smoketest_domain', 'address', 'example.org']) self.cli_set(['firewall', 'group', 'interface-group', 'smoketest_interface', 'interface', 'eth0']) self.cli_set(['firewall', 'group', 'interface-group', 'smoketest_interface', 'interface', 'vtun0']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'source', 'group', 'network-group', 'smoketest_network']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'destination', 'address', '172.16.10.10']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'destination', 'group', 'port-group', 'smoketest_port']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'protocol', 'tcp_udp']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '2', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '2', 'source', 'group', 'mac-group', 'smoketest_mac']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'source', 'group', 'domain-group', 'smoketest_domain']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'outbound-interface', 'group', '!smoketest_interface']) self.cli_commit() self.wait_for_domain_resolver('ip vyos_filter', 'D_smoketest_domain', '192.0.2.5') nftables_search = [ ['ip saddr @N_smoketest_network', 'ip daddr 172.16.10.10', 'th dport @P_smoketest_port', 'accept'], ['elements = { 172.16.99.0/24 }'], ['elements = { 53, 123 }'], ['ether saddr @M_smoketest_mac', 'accept'], ['elements = { 00:01:02:03:04:05 }'], ['set D_smoketest_domain'], ['elements = { 192.0.2.5, 192.0.2.8,'], ['192.0.2.10, 192.0.2.11 }'], ['ip saddr @D_smoketest_domain', 'accept'], ['oifname != @I_smoketest_interface', 'accept'] ] self.verify_nftables(nftables_search, 'ip vyos_filter') self.cli_delete(['system', 'static-host-mapping']) self.cli_commit() def test_nested_groups(self): self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network', 'network', '172.16.99.0/24']) self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network1', 'network', '172.16.101.0/24']) self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network1', 'include', 'smoketest_network']) self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port', 'port', '53']) self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port1', 'port', '123']) self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port1', 'include', 'smoketest_port']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'source', 'group', 'network-group', 'smoketest_network1']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'destination', 'group', 'port-group', 'smoketest_port1']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'protocol', 'tcp_udp']) self.cli_commit() # Test circular includes self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network', 'include', 'smoketest_network1']) with self.assertRaises(ConfigSessionError): self.cli_commit() self.cli_delete(['firewall', 'group', 'network-group', 'smoketest_network', 'include', 'smoketest_network1']) nftables_search = [ ['ip saddr @N_smoketest_network1', 'th dport @P_smoketest_port1', 'accept'], ['elements = { 172.16.99.0/24, 172.16.101.0/24 }'], ['elements = { 53, 123 }'] ] self.verify_nftables(nftables_search, 'ip vyos_filter') def test_ipv4_basic_rules(self): name = 'smoketest' interface = 'eth0' interface_inv = '!eth0' interface_wc = 'l2tp*' mss_range = '501-1460' conn_mark = '555' self.cli_set(['firewall', 'ipv4', 'name', name, 'default-action', 'drop']) self.cli_set(['firewall', 'ipv4', 'name', name, 'default-log']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'source', 'address', '172.16.20.10']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'destination', 'address', '172.16.10.10']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'log']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'log-options', 'level', 'debug']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'ttl', 'eq', '15']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'action', 'reject']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'protocol', 'tcp']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'destination', 'port', '8888']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'log']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'log-options', 'level', 'err']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'tcp', 'flags', 'syn']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'tcp', 'flags', 'not', 'ack']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'ttl', 'gt', '102']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'default-action', 'drop']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'default-log']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'protocol', 'tcp']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'destination', 'port', '22']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'limit', 'rate', '5/minute']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'log']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'action', 'drop']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'protocol', 'tcp']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'destination', 'port', '22']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'recent', 'count', '10']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'recent', 'time', 'minute']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'packet-type', 'host']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'protocol', 'tcp']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'tcp', 'flags', 'syn']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'tcp', 'mss', mss_range]) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'packet-type', 'broadcast']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'inbound-interface', 'name', interface_wc]) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '6', 'action', 'return']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '6', 'protocol', 'gre']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '6', 'connection-mark', conn_mark]) self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'default-action', 'drop']) self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'default-log']) self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '5', 'action', 'drop']) self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '5', 'protocol', 'gre']) self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '5', 'outbound-interface', 'name', interface_inv]) self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '6', 'action', 'return']) self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '6', 'protocol', 'icmp']) self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '6', 'connection-mark', conn_mark]) self.cli_set(['firewall', 'ipv4', 'output', 'raw', 'default-action', 'drop']) self.cli_set(['firewall', 'ipv4', 'output', 'raw', 'rule', '1', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'output', 'raw', 'rule', '1', 'protocol', 'udp']) - self.cli_set(['firewall', 'ipv4', 'prerouting', 'raw', 'rule', '1', 'action', 'drop']) + self.cli_set(['firewall', 'ipv4', 'prerouting', 'raw', 'rule', '1', 'action', 'notrack']) self.cli_set(['firewall', 'ipv4', 'prerouting', 'raw', 'rule', '1', 'protocol', 'tcp']) self.cli_set(['firewall', 'ipv4', 'prerouting', 'raw', 'rule', '1', 'destination', 'port', '23']) self.cli_commit() mark_hex = "{0:#010x}".format(int(conn_mark)) nftables_search = [ ['chain VYOS_FORWARD_filter'], ['type filter hook forward priority filter; policy accept;'], ['tcp dport 22', 'limit rate 5/minute', 'accept'], ['tcp dport 22', 'add @RECENT_FWD_filter_4 { ip saddr limit rate over 10/minute burst 10 packets }', 'meta pkttype host', 'drop'], ['log prefix "[ipv4-FWD-filter-default-D]"','FWD-filter default-action drop', 'drop'], ['chain VYOS_INPUT_filter'], ['type filter hook input priority filter; policy accept;'], ['tcp flags & syn == syn', f'tcp option maxseg size {mss_range}', f'iifname "{interface_wc}"', 'meta pkttype broadcast', 'accept'], ['meta l4proto gre', f'ct mark {mark_hex}', 'return'], ['INP-filter default-action accept', 'accept'], ['chain VYOS_OUTPUT_filter'], ['type filter hook output priority filter; policy accept;'], ['meta l4proto gre', f'oifname != "{interface}"', 'drop'], ['meta l4proto icmp', f'ct mark {mark_hex}', 'return'], ['log prefix "[ipv4-OUT-filter-default-D]"','OUT-filter default-action drop', 'drop'], ['chain VYOS_OUTPUT_raw'], ['type filter hook output priority raw; policy accept;'], ['udp', 'accept'], ['OUT-raw default-action drop', 'drop'], ['chain VYOS_PREROUTING_raw'], ['type filter hook prerouting priority raw; policy accept;'], - ['tcp dport 23', 'drop'], + ['tcp dport 23', 'notrack'], ['PRE-raw default-action accept', 'accept'], ['chain NAME_smoketest'], ['saddr 172.16.20.10', 'daddr 172.16.10.10', 'log prefix "[ipv4-NAM-smoketest-1-A]" log level debug', 'ip ttl 15', 'accept'], ['tcp flags syn / syn,ack', 'tcp dport 8888', 'log prefix "[ipv4-NAM-smoketest-2-R]" log level err', 'ip ttl > 102', 'reject'], ['log prefix "[ipv4-smoketest-default-D]"','smoketest default-action', 'drop'] ] self.verify_nftables(nftables_search, 'ip vyos_filter') def test_ipv4_advanced(self): name = 'smoketest-adv' name2 = 'smoketest-adv2' interface = 'eth0' self.cli_set(['firewall', 'ipv4', 'name', name, 'default-action', 'drop']) self.cli_set(['firewall', 'ipv4', 'name', name, 'default-log']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'packet-length', '64']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'packet-length', '512']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'packet-length', '1024']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'dscp', '17']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'dscp', '52']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'log']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'log-options', 'group', '66']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'log-options', 'snapshot-length', '6666']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'log-options', 'queue-threshold','32000']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '7', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '7', 'packet-length', '1-30000']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '7', 'packet-length-exclude', '60000-65535']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '7', 'dscp', '3-11']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '7', 'dscp-exclude', '21-25']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'default-action', 'drop']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'source', 'address', '198.51.100.1']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'mark', '1010']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'action', 'jump']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'jump-target', name]) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '2', 'protocol', 'tcp']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '2', 'mark', '!98765']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '2', 'action', 'queue']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '2', 'queue', '3']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '3', 'protocol', 'udp']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '3', 'action', 'queue']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '3', 'queue-options', 'fanout']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '3', 'queue-options', 'bypass']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '3', 'queue', '0-15']) self.cli_commit() nftables_search = [ ['chain VYOS_FORWARD_filter'], ['type filter hook forward priority filter; policy accept;'], ['ip saddr 198.51.100.1', 'meta mark 0x000003f2', f'jump NAME_{name}'], ['FWD-filter default-action drop', 'drop'], ['chain VYOS_INPUT_filter'], ['type filter hook input priority filter; policy accept;'], ['meta mark != 0x000181cd', 'meta l4proto tcp','queue to 3'], ['meta l4proto udp','queue flags bypass,fanout to 0-15'], ['INP-filter default-action accept', 'accept'], [f'chain NAME_{name}'], ['ip length { 64, 512, 1024 }', 'ip dscp { 0x11, 0x34 }', f'log prefix "[ipv4-NAM-{name}-6-A]" log group 66 snaplen 6666 queue-threshold 32000', 'accept'], ['ip length 1-30000', 'ip length != 60000-65535', 'ip dscp 0x03-0x0b', 'ip dscp != 0x15-0x19', 'accept'], [f'log prefix "[ipv4-{name}-default-D]"', 'drop'] ] self.verify_nftables(nftables_search, 'ip vyos_filter') def test_ipv4_synproxy(self): tcp_mss = '1460' tcp_wscale = '7' dport = '22' self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'action', 'drop']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'protocol', 'tcp']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'destination', 'port', dport]) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'synproxy', 'tcp', 'mss', tcp_mss]) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'synproxy', 'tcp', 'window-scale', tcp_wscale]) with self.assertRaises(ConfigSessionError): self.cli_commit() self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'action', 'synproxy']) self.cli_commit() nftables_search = [ [f'tcp dport {dport} ct state invalid,untracked', f'synproxy mss {tcp_mss} wscale {tcp_wscale} timestamp sack-perm'] ] self.verify_nftables(nftables_search, 'ip vyos_filter') def test_ipv4_mask(self): name = 'smoketest-mask' interface = 'eth0' self.cli_set(['firewall', 'group', 'address-group', 'mask_group', 'address', '1.1.1.1']) self.cli_set(['firewall', 'ipv4', 'name', name, 'default-action', 'drop']) self.cli_set(['firewall', 'ipv4', 'name', name, 'default-log']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'action', 'drop']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'destination', 'address', '0.0.1.2']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'destination', 'address-mask', '0.0.255.255']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'source', 'address', '!0.0.3.4']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'source', 'address-mask', '0.0.255.255']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'action', 'drop']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'source', 'group', 'address-group', 'mask_group']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'source', 'address-mask', '0.0.255.255']) self.cli_commit() nftables_search = [ [f'daddr & 0.0.255.255 == 0.0.1.2'], [f'saddr & 0.0.255.255 != 0.0.3.4'], [f'saddr & 0.0.255.255 == @A_mask_group'] ] self.verify_nftables(nftables_search, 'ip vyos_filter') def test_ipv4_dynamic_groups(self): group01 = 'knock01' group02 = 'allowed' self.cli_set(['firewall', 'group', 'dynamic-group', 'address-group', group01]) self.cli_set(['firewall', 'group', 'dynamic-group', 'address-group', group02]) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'action', 'drop']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'protocol', 'tcp']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'destination', 'port', '5151']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'add-address-to-group', 'source-address', 'address-group', group01]) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'add-address-to-group', 'source-address', 'timeout', '30s']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '20', 'action', 'drop']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '20', 'protocol', 'tcp']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '20', 'destination', 'port', '7272']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '20', 'source', 'group', 'dynamic-address-group', group01]) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '20', 'add-address-to-group', 'source-address', 'address-group', group02]) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '20', 'add-address-to-group', 'source-address', 'timeout', '5m']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '30', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '30', 'protocol', 'tcp']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '30', 'destination', 'port', '22']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '30', 'source', 'group', 'dynamic-address-group', group02]) self.cli_commit() nftables_search = [ [f'DA_{group01}'], [f'DA_{group02}'], ['type ipv4_addr'], ['flags dynamic,timeout'], ['chain VYOS_INPUT_filter {'], ['type filter hook input priority filter', 'policy accept'], ['tcp dport 5151', f'update @DA_{group01}', '{ ip saddr timeout 30s }', 'drop'], ['tcp dport 7272', f'ip saddr @DA_{group01}', f'update @DA_{group02}', '{ ip saddr timeout 5m }', 'drop'], ['tcp dport 22', f'ip saddr @DA_{group02}', 'accept'] ] self.verify_nftables(nftables_search, 'ip vyos_filter') def test_ipv6_basic_rules(self): name = 'v6-smoketest' interface = 'eth0' self.cli_set(['firewall', 'global-options', 'state-policy', 'established', 'action', 'accept']) self.cli_set(['firewall', 'global-options', 'state-policy', 'related', 'action', 'accept']) self.cli_set(['firewall', 'global-options', 'state-policy', 'invalid', 'action', 'drop']) self.cli_set(['firewall', 'ipv6', 'name', name, 'default-action', 'drop']) self.cli_set(['firewall', 'ipv6', 'name', name, 'default-log']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'action', 'accept']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'source', 'address', '2002::1']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'destination', 'address', '2002::1:1']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'log']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'log-options', 'level', 'crit']) self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'default-action', 'accept']) self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'default-log']) self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '2', 'action', 'reject']) self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '2', 'protocol', 'tcp_udp']) self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '2', 'destination', 'port', '8888']) self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '2', 'inbound-interface', 'name', interface]) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '3', 'action', 'accept']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '3', 'protocol', 'udp']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '3', 'source', 'address', '2002::1:2']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '3', 'inbound-interface', 'name', interface]) self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'default-action', 'drop']) self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'default-log']) self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'rule', '3', 'action', 'return']) self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'rule', '3', 'protocol', 'gre']) self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'rule', '3', 'outbound-interface', 'name', interface]) self.cli_set(['firewall', 'ipv6', 'output', 'raw', 'default-action', 'drop']) - self.cli_set(['firewall', 'ipv6', 'output', 'raw', 'rule', '1', 'action', 'accept']) + self.cli_set(['firewall', 'ipv6', 'output', 'raw', 'rule', '1', 'action', 'notrack']) self.cli_set(['firewall', 'ipv6', 'output', 'raw', 'rule', '1', 'protocol', 'udp']) self.cli_set(['firewall', 'ipv6', 'prerouting', 'raw', 'rule', '1', 'action', 'drop']) self.cli_set(['firewall', 'ipv6', 'prerouting', 'raw', 'rule', '1', 'protocol', 'tcp']) self.cli_set(['firewall', 'ipv6', 'prerouting', 'raw', 'rule', '1', 'destination', 'port', '23']) self.cli_commit() nftables_search = [ ['chain VYOS_IPV6_FORWARD_filter'], ['type filter hook forward priority filter; policy accept;'], ['meta l4proto { tcp, udp }', 'th dport 8888', f'iifname "{interface}"', 'reject'], ['log prefix "[ipv6-FWD-filter-default-A]"','FWD-filter default-action accept', 'accept'], ['chain VYOS_IPV6_INPUT_filter'], ['type filter hook input priority filter; policy accept;'], ['meta l4proto udp', 'ip6 saddr 2002::1:2', f'iifname "{interface}"', 'accept'], ['INP-filter default-action accept', 'accept'], ['chain VYOS_IPV6_OUTPUT_filter'], ['type filter hook output priority filter; policy accept;'], ['meta l4proto gre', f'oifname "{interface}"', 'return'], ['log prefix "[ipv6-OUT-filter-default-D]"','OUT-filter default-action drop', 'drop'], ['chain VYOS_IPV6_OUTPUT_raw'], ['type filter hook output priority raw; policy accept;'], - ['udp', 'accept'], + ['udp', 'notrack'], ['OUT-raw default-action drop', 'drop'], ['chain VYOS_IPV6_PREROUTING_raw'], ['type filter hook prerouting priority raw; policy accept;'], ['tcp dport 23', 'drop'], ['PRE-raw default-action accept', 'accept'], [f'chain NAME6_{name}'], ['saddr 2002::1', 'daddr 2002::1:1', 'log prefix "[ipv6-NAM-v6-smoketest-1-A]" log level crit', 'accept'], [f'"{name} default-action drop"', f'log prefix "[ipv6-{name}-default-D]"', 'drop'], ['jump VYOS_STATE_POLICY6'], ['chain VYOS_STATE_POLICY6'], ['ct state established', 'accept'], ['ct state invalid', 'drop'], ['ct state related', 'accept'] ] self.verify_nftables(nftables_search, 'ip6 vyos_filter') def test_ipv6_advanced(self): name = 'v6-smoketest-adv' name2 = 'v6-smoketest-adv2' interface = 'eth0' self.cli_set(['firewall', 'ipv6', 'name', name, 'default-action', 'drop']) self.cli_set(['firewall', 'ipv6', 'name', name, 'default-log']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'action', 'accept']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'packet-length', '65']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'packet-length', '513']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'packet-length', '1025']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'dscp', '18']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'dscp', '53']) self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '4', 'action', 'accept']) self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '4', 'packet-length', '1-1999']) self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '4', 'packet-length-exclude', '60000-65535']) self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '4', 'dscp', '4-14']) self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '4', 'dscp-exclude', '31-35']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'default-action', 'accept']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '1', 'source', 'address', '2001:db8::/64']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '1', 'mark', '!6655-7766']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '1', 'action', 'jump']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '1', 'jump-target', name]) self.cli_commit() nftables_search = [ ['chain VYOS_IPV6_FORWARD_filter'], ['type filter hook forward priority filter; policy accept;'], ['ip6 length 1-1999', 'ip6 length != 60000-65535', 'ip6 dscp 0x04-0x0e', 'ip6 dscp != 0x1f-0x23', 'accept'], ['chain VYOS_IPV6_INPUT_filter'], ['type filter hook input priority filter; policy accept;'], ['ip6 saddr 2001:db8::/64', 'meta mark != 0x000019ff-0x00001e56', f'jump NAME6_{name}'], [f'chain NAME6_{name}'], ['ip6 length { 65, 513, 1025 }', 'ip6 dscp { af21, 0x35 }', 'accept'], [f'log prefix "[ipv6-{name}-default-D]"', 'drop'] ] self.verify_nftables(nftables_search, 'ip6 vyos_filter') def test_ipv6_mask(self): name = 'v6-smoketest-mask' interface = 'eth0' self.cli_set(['firewall', 'group', 'ipv6-address-group', 'mask_group', 'address', '::beef']) self.cli_set(['firewall', 'ipv6', 'name', name, 'default-action', 'drop']) self.cli_set(['firewall', 'ipv6', 'name', name, 'default-log']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'action', 'drop']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'destination', 'address', '::1111:2222:3333:4444']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'destination', 'address-mask', '::ffff:ffff:ffff:ffff']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '2', 'action', 'accept']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '2', 'source', 'address', '!::aaaa:bbbb:cccc:dddd']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '2', 'source', 'address-mask', '::ffff:ffff:ffff:ffff']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'action', 'drop']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'source', 'group', 'address-group', 'mask_group']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'source', 'address-mask', '::ffff:ffff:ffff:ffff']) self.cli_commit() nftables_search = [ ['daddr & ::ffff:ffff:ffff:ffff == ::1111:2222:3333:4444'], ['saddr & ::ffff:ffff:ffff:ffff != ::aaaa:bbbb:cccc:dddd'], ['saddr & ::ffff:ffff:ffff:ffff == @A6_mask_group'] ] self.verify_nftables(nftables_search, 'ip6 vyos_filter') def test_ipv6_dynamic_groups(self): group01 = 'knock01' group02 = 'allowed' self.cli_set(['firewall', 'group', 'dynamic-group', 'ipv6-address-group', group01]) self.cli_set(['firewall', 'group', 'dynamic-group', 'ipv6-address-group', group02]) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '10', 'action', 'drop']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '10', 'protocol', 'tcp']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '10', 'destination', 'port', '5151']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '10', 'add-address-to-group', 'source-address', 'address-group', group01]) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '10', 'add-address-to-group', 'source-address', 'timeout', '30s']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '20', 'action', 'drop']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '20', 'protocol', 'tcp']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '20', 'destination', 'port', '7272']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '20', 'source', 'group', 'dynamic-address-group', group01]) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '20', 'add-address-to-group', 'source-address', 'address-group', group02]) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '20', 'add-address-to-group', 'source-address', 'timeout', '5m']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '30', 'action', 'accept']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '30', 'protocol', 'tcp']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '30', 'destination', 'port', '22']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '30', 'source', 'group', 'dynamic-address-group', group02]) self.cli_commit() nftables_search = [ [f'DA6_{group01}'], [f'DA6_{group02}'], ['type ipv6_addr'], ['flags dynamic,timeout'], ['chain VYOS_IPV6_INPUT_filter {'], ['type filter hook input priority filter', 'policy accept'], ['tcp dport 5151', f'update @DA6_{group01}', '{ ip6 saddr timeout 30s }', 'drop'], ['tcp dport 7272', f'ip6 saddr @DA6_{group01}', f'update @DA6_{group02}', '{ ip6 saddr timeout 5m }', 'drop'], ['tcp dport 22', f'ip6 saddr @DA6_{group02}', 'accept'] ] self.verify_nftables(nftables_search, 'ip6 vyos_filter') def test_ipv4_global_state(self): self.cli_set(['firewall', 'global-options', 'state-policy', 'established', 'action', 'accept']) self.cli_set(['firewall', 'global-options', 'state-policy', 'related', 'action', 'accept']) self.cli_set(['firewall', 'global-options', 'state-policy', 'invalid', 'action', 'drop']) self.cli_commit() nftables_search = [ ['jump VYOS_STATE_POLICY'], ['chain VYOS_STATE_POLICY'], ['ct state established', 'accept'], ['ct state invalid', 'drop'], ['ct state related', 'accept'] ] self.verify_nftables(nftables_search, 'ip vyos_filter') # Check conntrack is enabled from state-policy self.verify_nftables_chain([['accept']], 'ip vyos_conntrack', 'FW_CONNTRACK') self.verify_nftables_chain([['accept']], 'ip6 vyos_conntrack', 'FW_CONNTRACK') def test_ipv4_state_and_status_rules(self): name = 'smoketest-state' self.cli_set(['firewall', 'ipv4', 'name', name, 'default-action', 'drop']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'state', 'established']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'state', 'related']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'action', 'reject']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'state', 'invalid']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'state', 'new']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'connection-status', 'nat', 'destination']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '4', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '4', 'state', 'new']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '4', 'state', 'established']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '4', 'connection-status', 'nat', 'source']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '5', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '5', 'state', 'related']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '5', 'conntrack-helper', 'ftp']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '5', 'conntrack-helper', 'pptp']) self.cli_commit() nftables_search = [ ['ct state { established, related }', 'accept'], ['ct state invalid', 'reject'], ['ct state new', 'ct status dnat', 'accept'], ['ct state { established, new }', 'ct status snat', 'accept'], ['ct state related', 'ct helper { "ftp", "pptp" }', 'accept'], ['drop', f'comment "{name} default-action drop"'] ] self.verify_nftables(nftables_search, 'ip vyos_filter') # Check conntrack self.verify_nftables_chain([['accept']], 'ip vyos_conntrack', 'FW_CONNTRACK') self.verify_nftables_chain([['return']], 'ip6 vyos_conntrack', 'FW_CONNTRACK') def test_bridge_basic_rules(self): name = 'smoketest' interface_in = 'eth0' mac_address = '00:53:00:00:00:01' vlan_id = '12' vlan_prior = '3' self.cli_set(['firewall', 'bridge', 'name', name, 'default-action', 'accept']) self.cli_set(['firewall', 'bridge', 'name', name, 'default-log']) self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'action', 'accept']) self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'source', 'mac-address', mac_address]) self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'inbound-interface', 'name', interface_in]) self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'log']) self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'log-options', 'level', 'crit']) self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'default-action', 'drop']) self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'default-log']) self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '1', 'action', 'accept']) self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '1', 'vlan', 'id', vlan_id]) self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '2', 'action', 'jump']) self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '2', 'jump-target', name]) self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '2', 'vlan', 'priority', vlan_prior]) self.cli_commit() nftables_search = [ ['chain VYOS_FORWARD_filter'], ['type filter hook forward priority filter; policy accept;'], [f'vlan id {vlan_id}', 'accept'], [f'vlan pcp {vlan_prior}', f'jump NAME_{name}'], ['log prefix "[bri-FWD-filter-default-D]"', 'drop', 'FWD-filter default-action drop'], [f'chain NAME_{name}'], [f'ether saddr {mac_address}', f'iifname "{interface_in}"', f'log prefix "[bri-NAM-{name}-1-A]" log level crit', 'accept'], ['accept', f'{name} default-action accept'] ] self.verify_nftables(nftables_search, 'bridge vyos_filter') def test_source_validation(self): # Strict self.cli_set(['firewall', 'global-options', 'source-validation', 'strict']) self.cli_set(['firewall', 'global-options', 'ipv6-source-validation', 'strict']) self.cli_commit() nftables_strict_search = [ ['fib saddr . iif oif 0', 'drop'] ] self.verify_nftables_chain(nftables_strict_search, 'ip raw', 'vyos_global_rpfilter') self.verify_nftables_chain(nftables_strict_search, 'ip6 raw', 'vyos_global_rpfilter') # Loose self.cli_set(['firewall', 'global-options', 'source-validation', 'loose']) self.cli_set(['firewall', 'global-options', 'ipv6-source-validation', 'loose']) self.cli_commit() nftables_loose_search = [ ['fib saddr oif 0', 'drop'] ] self.verify_nftables_chain(nftables_loose_search, 'ip raw', 'vyos_global_rpfilter') self.verify_nftables_chain(nftables_loose_search, 'ip6 raw', 'vyos_global_rpfilter') def test_sysfs(self): for name, conf in sysfs_config.items(): paths = glob(conf['sysfs']) for path in paths: with open(path, 'r') as f: self.assertEqual(f.read().strip(), conf['default'], msg=path) self.cli_set(['firewall', 'global-options', name.replace("_", "-"), conf['test_value']]) self.cli_commit() for name, conf in sysfs_config.items(): paths = glob(conf['sysfs']) for path in paths: with open(path, 'r') as f: self.assertNotEqual(f.read().strip(), conf['default'], msg=path) + def test_timeout_sysctl(self): + timeout_config = { + 'net.netfilter.nf_conntrack_icmp_timeout' :{ + 'cli' : ['global-options', 'timeout', 'icmp'], + 'test_value' : '180', + 'default_value' : '30', + }, + 'net.netfilter.nf_conntrack_generic_timeout' :{ + 'cli' : ['global-options', 'timeout', 'other'], + 'test_value' : '1200', + 'default_value' : '600', + }, + 'net.netfilter.nf_conntrack_tcp_timeout_close_wait' :{ + 'cli' : ['global-options', 'timeout', 'tcp', 'close-wait'], + 'test_value' : '30', + 'default_value' : '60', + }, + 'net.netfilter.nf_conntrack_tcp_timeout_close' :{ + 'cli' : ['global-options', 'timeout', 'tcp', 'close'], + 'test_value' : '20', + 'default_value' : '10', + }, + 'net.netfilter.nf_conntrack_tcp_timeout_established' :{ + 'cli' : ['global-options', 'timeout', 'tcp', 'established'], + 'test_value' : '1000', + 'default_value' : '432000', + }, + 'net.netfilter.nf_conntrack_tcp_timeout_fin_wait' :{ + 'cli' : ['global-options', 'timeout', 'tcp', 'fin-wait'], + 'test_value' : '240', + 'default_value' : '120', + }, + 'net.netfilter.nf_conntrack_tcp_timeout_last_ack' :{ + 'cli' : ['global-options', 'timeout', 'tcp', 'last-ack'], + 'test_value' : '300', + 'default_value' : '30', + }, + 'net.netfilter.nf_conntrack_tcp_timeout_syn_recv' :{ + 'cli' : ['global-options', 'timeout', 'tcp', 'syn-recv'], + 'test_value' : '100', + 'default_value' : '60', + }, + 'net.netfilter.nf_conntrack_tcp_timeout_syn_sent' :{ + 'cli' : ['global-options', 'timeout', 'tcp', 'syn-sent'], + 'test_value' : '300', + 'default_value' : '120', + }, + 'net.netfilter.nf_conntrack_tcp_timeout_time_wait' :{ + 'cli' : ['global-options', 'timeout', 'tcp', 'time-wait'], + 'test_value' : '303', + 'default_value' : '120', + }, + 'net.netfilter.nf_conntrack_udp_timeout' :{ + 'cli' : ['global-options', 'timeout', 'udp', 'other'], + 'test_value' : '90', + 'default_value' : '30', + }, + 'net.netfilter.nf_conntrack_udp_timeout_stream' :{ + 'cli' : ['global-options', 'timeout', 'udp', 'stream'], + 'test_value' : '200', + 'default_value' : '180', + }, + } + + for parameter, parameter_config in timeout_config.items(): + self.cli_set(['firewall'] + parameter_config['cli'] + [parameter_config['test_value']]) + + # commit changes + self.cli_commit() + + # validate configuration + for parameter, parameter_config in timeout_config.items(): + tmp = parameter_config['test_value'] + self.assertEqual(get_sysctl(f'{parameter}'), tmp) + + # delete all configuration options and revert back to defaults + self.cli_delete(['firewall', 'global-options', 'timeout']) + self.cli_commit() + + # validate configuration + for parameter, parameter_config in timeout_config.items(): + self.assertEqual(get_sysctl(f'{parameter}'), parameter_config['default_value']) + ### Zone def test_zone_basic(self): self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'default-action', 'drop']) self.cli_set(['firewall', 'ipv6', 'name', 'smoketestv6', 'default-action', 'drop']) self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'interface', 'eth0']) self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'from', 'smoketest-local', 'firewall', 'name', 'smoketest']) self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'intra-zone-filtering', 'firewall', 'ipv6-name', 'smoketestv6']) self.cli_set(['firewall', 'zone', 'smoketest-local', 'local-zone']) self.cli_set(['firewall', 'zone', 'smoketest-local', 'from', 'smoketest-eth0', 'firewall', 'name', 'smoketest']) self.cli_set(['firewall', 'global-options', 'state-policy', 'established', 'action', 'accept']) self.cli_set(['firewall', 'global-options', 'state-policy', 'established', 'log']) self.cli_set(['firewall', 'global-options', 'state-policy', 'related', 'action', 'accept']) self.cli_set(['firewall', 'global-options', 'state-policy', 'invalid', 'action', 'drop']) self.cli_commit() nftables_search = [ ['chain VYOS_ZONE_FORWARD'], ['type filter hook forward priority filter + 1'], ['chain VYOS_ZONE_OUTPUT'], ['type filter hook output priority filter + 1'], ['chain VYOS_ZONE_LOCAL'], ['type filter hook input priority filter + 1'], ['chain VZONE_smoketest-eth0'], ['chain VZONE_smoketest-local_IN'], ['chain VZONE_smoketest-local_OUT'], ['oifname "eth0"', 'jump VZONE_smoketest-eth0'], ['jump VZONE_smoketest-local_IN'], ['jump VZONE_smoketest-local_OUT'], ['iifname "eth0"', 'jump NAME_smoketest'], ['oifname "eth0"', 'jump NAME_smoketest'], ['jump VYOS_STATE_POLICY'], ['chain VYOS_STATE_POLICY'], ['ct state established', 'log prefix "[STATE-POLICY-EST-A]"', 'accept'], ['ct state invalid', 'drop'], ['ct state related', 'accept'] ] nftables_search_v6 = [ ['chain VYOS_ZONE_FORWARD'], ['type filter hook forward priority filter + 1'], ['chain VYOS_ZONE_OUTPUT'], ['type filter hook output priority filter + 1'], ['chain VYOS_ZONE_LOCAL'], ['type filter hook input priority filter + 1'], ['chain VZONE_smoketest-eth0'], ['chain VZONE_smoketest-local_IN'], ['chain VZONE_smoketest-local_OUT'], ['oifname "eth0"', 'jump VZONE_smoketest-eth0'], ['jump VZONE_smoketest-local_IN'], ['jump VZONE_smoketest-local_OUT'], ['iifname "eth0"', 'jump NAME6_smoketestv6'], ['jump VYOS_STATE_POLICY6'], ['chain VYOS_STATE_POLICY6'], ['ct state established', 'log prefix "[STATE-POLICY-EST-A]"', 'accept'], ['ct state invalid', 'drop'], ['ct state related', 'accept'] ] self.verify_nftables(nftables_search, 'ip vyos_filter') self.verify_nftables(nftables_search_v6, 'ip6 vyos_filter') def test_flow_offload(self): self.cli_set(['interfaces', 'ethernet', 'eth0', 'vif', '10']) self.cli_set(['firewall', 'flowtable', 'smoketest', 'interface', 'eth0.10']) self.cli_set(['firewall', 'flowtable', 'smoketest', 'offload', 'hardware']) # QEMU virtual NIC does not support hw-tc-offload with self.assertRaises(ConfigSessionError): self.cli_commit() self.cli_set(['firewall', 'flowtable', 'smoketest', 'offload', 'software']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'action', 'offload']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'offload-target', 'smoketest']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'protocol', 'tcp_udp']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'state', 'established']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'state', 'related']) self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '1', 'action', 'offload']) self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '1', 'offload-target', 'smoketest']) self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '1', 'protocol', 'tcp_udp']) self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '1', 'state', 'established']) self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '1', 'state', 'related']) self.cli_commit() nftables_search = [ ['flowtable VYOS_FLOWTABLE_smoketest'], ['hook ingress priority filter'], ['devices = { eth0.10 }'], ['ct state { established, related }', 'meta l4proto { tcp, udp }', 'flow add @VYOS_FLOWTABLE_smoketest'], ] self.verify_nftables(nftables_search, 'ip vyos_filter') self.verify_nftables(nftables_search, 'ip6 vyos_filter') # Check conntrack self.verify_nftables_chain([['accept']], 'ip vyos_conntrack', 'FW_CONNTRACK') self.verify_nftables_chain([['accept']], 'ip6 vyos_conntrack', 'FW_CONNTRACK') def test_zone_flow_offload(self): self.cli_set(['firewall', 'flowtable', 'smoketest', 'interface', 'eth0']) self.cli_set(['firewall', 'flowtable', 'smoketest', 'offload', 'hardware']) # QEMU virtual NIC does not support hw-tc-offload with self.assertRaises(ConfigSessionError): self.cli_commit() self.cli_set(['firewall', 'flowtable', 'smoketest', 'offload', 'software']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'action', 'offload']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'offload-target', 'smoketest']) self.cli_set(['firewall', 'ipv6', 'name', 'smoketest', 'rule', '1', 'action', 'offload']) self.cli_set(['firewall', 'ipv6', 'name', 'smoketest', 'rule', '1', 'offload-target', 'smoketest']) self.cli_commit() nftables_search = [ ['chain NAME_smoketest'], ['flow add @VYOS_FLOWTABLE_smoketest'] ] self.verify_nftables(nftables_search, 'ip vyos_filter') nftables_search = [ ['chain NAME6_smoketest'], ['flow add @VYOS_FLOWTABLE_smoketest'] ] self.verify_nftables(nftables_search, 'ip6 vyos_filter') # Check conntrack self.verify_nftables_chain([['accept']], 'ip vyos_conntrack', 'FW_CONNTRACK') self.verify_nftables_chain([['accept']], 'ip6 vyos_conntrack', 'FW_CONNTRACK') if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_system_conntrack.py b/smoketest/scripts/cli/test_system_conntrack.py index c6d8a5436..3ae7b6217 100755 --- a/smoketest/scripts/cli/test_system_conntrack.py +++ b/smoketest/scripts/cli/test_system_conntrack.py @@ -1,344 +1,284 @@ #!/usr/bin/env python3 # # Copyright (C) 2021-2024 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 from base_vyostest_shim import VyOSUnitTestSHIM from vyos.firewall import find_nftables_rule from vyos.utils.file import read_file base_path = ['system', 'conntrack'] def get_sysctl(parameter): tmp = parameter.replace(r'.', r'/') return read_file(f'/proc/sys/{tmp}') class TestSystemConntrack(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): super(TestSystemConntrack, 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_conntrack_options(self): conntrack_config = { 'net.netfilter.nf_conntrack_expect_max' : { 'cli' : ['expect-table-size'], 'test_value' : '8192', 'default_value' : '2048', }, 'net.nf_conntrack_max' :{ 'cli' : ['table-size'], 'test_value' : '500000', 'default_value' : '262144', }, 'net.ipv4.tcp_max_syn_backlog' :{ 'cli' : ['tcp', 'half-open-connections'], 'test_value' : '2048', 'default_value' : '512', }, 'net.netfilter.nf_conntrack_tcp_loose' :{ 'cli' : ['tcp', 'loose'], 'test_value' : 'disable', 'default_value' : '1', }, 'net.netfilter.nf_conntrack_tcp_max_retrans' :{ 'cli' : ['tcp', 'max-retrans'], 'test_value' : '128', 'default_value' : '3', }, - 'net.netfilter.nf_conntrack_icmp_timeout' :{ - 'cli' : ['timeout', 'icmp'], - 'test_value' : '180', - 'default_value' : '30', - }, - 'net.netfilter.nf_conntrack_generic_timeout' :{ - 'cli' : ['timeout', 'other'], - 'test_value' : '1200', - 'default_value' : '600', - }, - 'net.netfilter.nf_conntrack_tcp_timeout_close_wait' :{ - 'cli' : ['timeout', 'tcp', 'close-wait'], - 'test_value' : '30', - 'default_value' : '60', - }, - 'net.netfilter.nf_conntrack_tcp_timeout_close' :{ - 'cli' : ['timeout', 'tcp', 'close'], - 'test_value' : '20', - 'default_value' : '10', - }, - 'net.netfilter.nf_conntrack_tcp_timeout_established' :{ - 'cli' : ['timeout', 'tcp', 'established'], - 'test_value' : '1000', - 'default_value' : '432000', - }, - 'net.netfilter.nf_conntrack_tcp_timeout_fin_wait' :{ - 'cli' : ['timeout', 'tcp', 'fin-wait'], - 'test_value' : '240', - 'default_value' : '120', - }, - 'net.netfilter.nf_conntrack_tcp_timeout_last_ack' :{ - 'cli' : ['timeout', 'tcp', 'last-ack'], - 'test_value' : '300', - 'default_value' : '30', - }, - 'net.netfilter.nf_conntrack_tcp_timeout_syn_recv' :{ - 'cli' : ['timeout', 'tcp', 'syn-recv'], - 'test_value' : '100', - 'default_value' : '60', - }, - 'net.netfilter.nf_conntrack_tcp_timeout_syn_sent' :{ - 'cli' : ['timeout', 'tcp', 'syn-sent'], - 'test_value' : '300', - 'default_value' : '120', - }, - 'net.netfilter.nf_conntrack_tcp_timeout_time_wait' :{ - 'cli' : ['timeout', 'tcp', 'time-wait'], - 'test_value' : '303', - 'default_value' : '120', - }, - 'net.netfilter.nf_conntrack_udp_timeout' :{ - 'cli' : ['timeout', 'udp', 'other'], - 'test_value' : '90', - 'default_value' : '30', - }, - 'net.netfilter.nf_conntrack_udp_timeout_stream' :{ - 'cli' : ['timeout', 'udp', 'stream'], - 'test_value' : '200', - 'default_value' : '180', - }, } for parameter, parameter_config in conntrack_config.items(): self.cli_set(base_path + parameter_config['cli'] + [parameter_config['test_value']]) # commit changes self.cli_commit() # validate configuration for parameter, parameter_config in conntrack_config.items(): tmp = parameter_config['test_value'] # net.netfilter.nf_conntrack_tcp_loose has a fancy "disable" value, # make this work if tmp == 'disable': tmp = '0' self.assertEqual(get_sysctl(f'{parameter}'), tmp) # delete all configuration options and revert back to defaults self.cli_delete(base_path) self.cli_commit() # validate configuration for parameter, parameter_config in conntrack_config.items(): self.assertEqual(get_sysctl(f'{parameter}'), parameter_config['default_value']) def test_conntrack_module_enable(self): # conntrack helper modules are disabled by default modules = { 'ftp': { 'driver': ['nf_nat_ftp', 'nf_conntrack_ftp'], 'nftables': ['ct helper set "ftp_tcp"'] }, 'h323': { 'driver': ['nf_nat_h323', 'nf_conntrack_h323'], 'nftables': ['ct helper set "ras_udp"', 'ct helper set "q931_tcp"'] }, 'nfs': { 'nftables': ['ct helper set "rpc_tcp"', 'ct helper set "rpc_udp"'] }, 'pptp': { 'driver': ['nf_nat_pptp', 'nf_conntrack_pptp'], 'nftables': ['ct helper set "pptp_tcp"'] }, 'rtsp': { 'driver': ['nf_nat_rtsp', 'nf_conntrack_rtsp'], 'nftables': ['ct helper set "rtsp_tcp"'] }, 'sip': { 'driver': ['nf_nat_sip', 'nf_conntrack_sip'], 'nftables': ['ct helper set "sip_tcp"', 'ct helper set "sip_udp"'] }, 'sqlnet': { 'nftables': ['ct helper set "tns_tcp"'] }, 'tftp': { 'driver': ['nf_nat_tftp', 'nf_conntrack_tftp'], 'nftables': ['ct helper set "tftp_udp"'] }, } # load modules for module in modules: self.cli_set(base_path + ['modules', module]) # commit changes self.cli_commit() # verify modules are loaded on the system for module, module_options in modules.items(): if 'driver' in module_options: for driver in module_options['driver']: self.assertTrue(os.path.isdir(f'/sys/module/{driver}')) if 'nftables' in module_options: for rule in module_options['nftables']: self.assertTrue(find_nftables_rule('ip vyos_conntrack', 'VYOS_CT_HELPER', [rule]) != None) # unload modules for module in modules: self.cli_delete(base_path + ['modules', module]) # commit changes self.cli_commit() # verify modules are not loaded on the system for module, module_options in modules.items(): if 'driver' in module_options: for driver in module_options['driver']: self.assertFalse(os.path.isdir(f'/sys/module/{driver}')) if 'nftables' in module_options: for rule in module_options['nftables']: self.assertTrue(find_nftables_rule('ip vyos_conntrack', 'VYOS_CT_HELPER', [rule]) == None) def test_conntrack_hash_size(self): hash_size = '65536' hash_size_default = '32768' self.cli_set(base_path + ['hash-size', hash_size]) # commit changes self.cli_commit() # verify new configuration - only effective after reboot, but # a valid config file is sufficient tmp = read_file('/etc/modprobe.d/vyatta_nf_conntrack.conf') self.assertIn(hash_size, tmp) # Test default value by deleting the configuration self.cli_delete(base_path + ['hash-size']) # commit changes self.cli_commit() # verify new configuration - only effective after reboot, but # a valid config file is sufficient tmp = read_file('/etc/modprobe.d/vyatta_nf_conntrack.conf') self.assertIn(hash_size_default, tmp) def test_conntrack_ignore(self): address_group = 'conntracktest' address_group_member = '192.168.0.1' ipv6_address_group = 'conntracktest6' ipv6_address_group_member = 'dead:beef::1' self.cli_set(['firewall', 'group', 'address-group', address_group, 'address', address_group_member]) self.cli_set(['firewall', 'group', 'ipv6-address-group', ipv6_address_group, 'address', ipv6_address_group_member]) self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'source', 'address', '192.0.2.1']) self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'destination', 'address', '192.0.2.2']) self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'destination', 'port', '22']) self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'protocol', 'tcp']) self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'tcp', 'flags', 'syn']) self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '2', 'source', 'address', '192.0.2.1']) self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '2', 'destination', 'group', 'address-group', address_group]) self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '11', 'source', 'address', 'fe80::1']) self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '11', 'destination', 'address', 'fe80::2']) self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '11', 'destination', 'port', '22']) self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '11', 'protocol', 'tcp']) self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '12', 'source', 'address', 'fe80::1']) self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '12', 'destination', 'group', 'address-group', ipv6_address_group]) self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '13', 'source', 'address', 'fe80::1']) self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '13', 'destination', 'address', '!fe80::3']) self.cli_commit() nftables_search = [ ['ip saddr 192.0.2.1', 'ip daddr 192.0.2.2', 'tcp dport 22', 'tcp flags & syn == syn', 'notrack'], ['ip saddr 192.0.2.1', 'ip daddr @A_conntracktest', 'notrack'] ] nftables6_search = [ ['ip6 saddr fe80::1', 'ip6 daddr fe80::2', 'tcp dport 22', 'notrack'], ['ip6 saddr fe80::1', 'ip6 daddr @A6_conntracktest6', 'notrack'], ['ip6 saddr fe80::1', 'ip6 daddr != fe80::3', 'notrack'] ] self.verify_nftables(nftables_search, 'ip vyos_conntrack') self.verify_nftables(nftables6_search, 'ip6 vyos_conntrack') self.cli_delete(['firewall']) def test_conntrack_timeout_custom(self): self.cli_set(base_path + ['timeout', 'custom', 'ipv4', 'rule', '1', 'source', 'address', '192.0.2.1']) self.cli_set(base_path + ['timeout', 'custom', 'ipv4', 'rule', '1', 'destination', 'address', '192.0.2.2']) self.cli_set(base_path + ['timeout', 'custom', 'ipv4', 'rule', '1', 'destination', 'port', '22']) self.cli_set(base_path + ['timeout', 'custom', 'ipv4', 'rule', '1', 'protocol', 'tcp', 'syn-sent', '77']) self.cli_set(base_path + ['timeout', 'custom', 'ipv4', 'rule', '1', 'protocol', 'tcp', 'close', '88']) self.cli_set(base_path + ['timeout', 'custom', 'ipv4', 'rule', '1', 'protocol', 'tcp', 'established', '99']) self.cli_set(base_path + ['timeout', 'custom', 'ipv4', 'rule', '2', 'inbound-interface', 'eth1']) self.cli_set(base_path + ['timeout', 'custom', 'ipv4', 'rule', '2', 'source', 'address', '198.51.100.1']) self.cli_set(base_path + ['timeout', 'custom', 'ipv4', 'rule', '2', 'protocol', 'udp', 'unreplied', '55']) self.cli_set(base_path + ['timeout', 'custom', 'ipv6', 'rule', '1', 'source', 'address', '2001:db8::1']) self.cli_set(base_path + ['timeout', 'custom', 'ipv6', 'rule', '1', 'inbound-interface', 'eth2']) self.cli_set(base_path + ['timeout', 'custom', 'ipv6', 'rule', '1', 'protocol', 'tcp', 'time-wait', '22']) self.cli_set(base_path + ['timeout', 'custom', 'ipv6', 'rule', '1', 'protocol', 'tcp', 'last-ack', '33']) self.cli_commit() nftables_search = [ ['ct timeout ct-timeout-1 {'], ['protocol tcp'], ['policy = { syn_sent : 1m17s, established : 1m39s, close : 1m28s }'], ['ct timeout ct-timeout-2 {'], ['protocol udp'], ['policy = { unreplied : 55s }'], ['chain VYOS_CT_TIMEOUT {'], ['ip saddr 192.0.2.1', 'ip daddr 192.0.2.2', 'tcp dport 22', 'ct timeout set "ct-timeout-1"'], ['iifname "eth1"', 'meta l4proto udp', 'ip saddr 198.51.100.1', 'ct timeout set "ct-timeout-2"'] ] nftables6_search = [ ['ct timeout ct-timeout-1 {'], ['protocol tcp'], ['policy = { last_ack : 33s, time_wait : 22s }'], ['chain VYOS_CT_TIMEOUT {'], ['iifname "eth2"', 'meta l4proto tcp', 'ip6 saddr 2001:db8::1', 'ct timeout set "ct-timeout-1"'] ] self.verify_nftables(nftables_search, 'ip vyos_conntrack') self.verify_nftables(nftables6_search, 'ip6 vyos_conntrack') self.cli_delete(['firewall']) if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index acf3805d2..4c289b921 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -1,524 +1,495 @@ #!/usr/bin/env python3 # # Copyright (C) 2021-2024 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 glob import glob from sys import exit from vyos.base import Warning from vyos.config import Config from vyos.configdict import is_node_changed from vyos.configdiff import get_config_diff, Diff from vyos.configdep import set_dependents, call_dependents from vyos.configverify import verify_interface_exists from vyos.ethtool import Ethtool from vyos.firewall import fqdn_config_parse from vyos.firewall import geoip_update from vyos.template import render from vyos.utils.dict import dict_search_args from vyos.utils.dict import dict_search_recursive from vyos.utils.process import call +from vyos.utils.process import cmd from vyos.utils.process import rc_cmd from vyos import ConfigError from vyos import airbag airbag.enable() nftables_conf = '/run/nftables.conf' - -sysfs_config = { - 'all_ping': {'sysfs': '/proc/sys/net/ipv4/icmp_echo_ignore_all', 'enable': '0', 'disable': '1'}, - 'broadcast_ping': {'sysfs': '/proc/sys/net/ipv4/icmp_echo_ignore_broadcasts', 'enable': '0', 'disable': '1'}, - 'directed_broadcast' : {'sysfs': '/proc/sys/net/ipv4/conf/all/bc_forwarding', 'enable': '1', 'disable': '0'}, - 'ip_src_route': {'sysfs': '/proc/sys/net/ipv4/conf/*/accept_source_route'}, - 'ipv6_receive_redirects': {'sysfs': '/proc/sys/net/ipv6/conf/*/accept_redirects'}, - 'ipv6_src_route': {'sysfs': '/proc/sys/net/ipv6/conf/*/accept_source_route', 'enable': '0', 'disable': '-1'}, - 'log_martians': {'sysfs': '/proc/sys/net/ipv4/conf/all/log_martians'}, - 'receive_redirects': {'sysfs': '/proc/sys/net/ipv4/conf/*/accept_redirects'}, - 'send_redirects': {'sysfs': '/proc/sys/net/ipv4/conf/*/send_redirects'}, - 'syn_cookies': {'sysfs': '/proc/sys/net/ipv4/tcp_syncookies'}, - 'twa_hazards_protection': {'sysfs': '/proc/sys/net/ipv4/tcp_rfc1337'} -} +sysctl_file = r'/run/sysctl/10-vyos-firewall.conf' valid_groups = [ 'address_group', 'domain_group', 'network_group', 'port_group', 'interface_group' ] nested_group_types = [ 'address_group', 'network_group', 'mac_group', 'port_group', 'ipv6_address_group', 'ipv6_network_group' ] snmp_change_type = { 'unknown': 0, 'add': 1, 'delete': 2, 'change': 3 } snmp_event_source = 1 snmp_trap_mib = 'VYATTA-TRAP-MIB' snmp_trap_name = 'mgmtEventTrap' def geoip_updated(conf, firewall): diff = get_config_diff(conf) node_diff = diff.get_child_nodes_diff(['firewall'], expand_nodes=Diff.DELETE, recursive=True) out = { 'name': [], 'ipv6_name': [], 'deleted_name': [], 'deleted_ipv6_name': [] } updated = False for key, path in dict_search_recursive(firewall, 'geoip'): set_name = f'GEOIP_CC_{path[1]}_{path[2]}_{path[4]}' if (path[0] == 'ipv4'): out['name'].append(set_name) elif (path[0] == 'ipv6'): set_name = f'GEOIP_CC6_{path[1]}_{path[2]}_{path[4]}' out['ipv6_name'].append(set_name) updated = True if 'delete' in node_diff: for key, path in dict_search_recursive(node_diff['delete'], 'geoip'): set_name = f'GEOIP_CC_{path[1]}_{path[2]}_{path[4]}' if (path[0] == 'ipv4'): out['deleted_name'].append(set_name) elif (path[0] == 'ipv6'): set_name = f'GEOIP_CC_{path[1]}_{path[2]}_{path[4]}' out['deleted_ipv6_name'].append(set_name) updated = True if updated: return out return False def get_config(config=None): if config: conf = config else: conf = Config() base = ['firewall'] firewall = conf.get_config_dict(base, key_mangling=('-', '_'), no_tag_node_value_mangle=True, get_first_key=True, with_recursive_defaults=True) firewall['group_resync'] = bool('group' in firewall or is_node_changed(conf, base + ['group'])) if firewall['group_resync']: # Update nat and policy-route as firewall groups were updated set_dependents('group_resync', conf) firewall['geoip_updated'] = geoip_updated(conf, firewall) fqdn_config_parse(firewall) set_dependents('conntrack', conf) return firewall def verify_rule(firewall, rule_conf, ipv6): if 'action' not in rule_conf: raise ConfigError('Rule action must be defined') if 'jump' in rule_conf['action'] and 'jump_target' not in rule_conf: raise ConfigError('Action set to jump, but no jump-target specified') if 'jump_target' in rule_conf: if 'jump' not in rule_conf['action']: raise ConfigError('jump-target defined, but action jump needed and it is not defined') target = rule_conf['jump_target'] if not ipv6: if target not in dict_search_args(firewall, 'ipv4', 'name'): raise ConfigError(f'Invalid jump-target. Firewall name {target} does not exist on the system') else: if target not in dict_search_args(firewall, 'ipv6', 'name'): raise ConfigError(f'Invalid jump-target. Firewall ipv6 name {target} does not exist on the system') if rule_conf['action'] == 'offload': if 'offload_target' not in rule_conf: raise ConfigError('Action set to offload, but no offload-target specified') offload_target = rule_conf['offload_target'] if not dict_search_args(firewall, 'flowtable', offload_target): raise ConfigError(f'Invalid offload-target. Flowtable "{offload_target}" does not exist on the system') if rule_conf['action'] != 'synproxy' and 'synproxy' in rule_conf: raise ConfigError('"synproxy" option allowed only for action synproxy') if rule_conf['action'] == 'synproxy': if 'state' in rule_conf: raise ConfigError('For action "synproxy" state cannot be defined') if not rule_conf.get('synproxy', {}).get('tcp'): raise ConfigError('synproxy TCP MSS is not defined') if rule_conf.get('protocol', {}) != 'tcp': raise ConfigError('For action "synproxy" the protocol must be set to TCP') if 'queue_options' in rule_conf: if 'queue' not in rule_conf['action']: raise ConfigError('queue-options defined, but action queue needed and it is not defined') if 'fanout' in rule_conf['queue_options'] and ('queue' not in rule_conf or '-' not in rule_conf['queue']): raise ConfigError('queue-options fanout defined, then queue needs to be defined as a range') if 'queue' in rule_conf and 'queue' not in rule_conf['action']: raise ConfigError('queue defined, but action queue needed and it is not defined') if 'fragment' in rule_conf: if {'match_frag', 'match_non_frag'} <= set(rule_conf['fragment']): raise ConfigError('Cannot specify both "match-frag" and "match-non-frag"') if 'limit' in rule_conf: if 'rate' in rule_conf['limit']: rate_int = re.sub(r'\D', '', rule_conf['limit']['rate']) if int(rate_int) < 1: raise ConfigError('Limit rate integer cannot be less than 1') if 'ipsec' in rule_conf: if {'match_ipsec', 'match_non_ipsec'} <= set(rule_conf['ipsec']): raise ConfigError('Cannot specify both "match-ipsec" and "match-non-ipsec"') if 'recent' in rule_conf: if not {'count', 'time'} <= set(rule_conf['recent']): raise ConfigError('Recent "count" and "time" values must be defined') tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags') if tcp_flags: if dict_search_args(rule_conf, 'protocol') != 'tcp': raise ConfigError('Protocol must be tcp when specifying tcp flags') not_flags = dict_search_args(rule_conf, 'tcp', 'flags', 'not') if not_flags: duplicates = [flag for flag in tcp_flags if flag in not_flags] if duplicates: raise ConfigError(f'Cannot match a tcp flag as set and not set') if 'protocol' in rule_conf: if rule_conf['protocol'] == 'icmp' and ipv6: raise ConfigError(f'Cannot match IPv4 ICMP protocol on IPv6, use ipv6-icmp') if rule_conf['protocol'] == 'ipv6-icmp' and not ipv6: raise ConfigError(f'Cannot match IPv6 ICMP protocol on IPv4, use icmp') for side in ['destination', 'source']: if side in rule_conf: side_conf = rule_conf[side] if len({'address', 'fqdn', 'geoip'} & set(side_conf)) > 1: raise ConfigError('Only one of address, fqdn or geoip can be specified') if 'group' in side_conf: if len({'address_group', 'network_group', 'domain_group'} & set(side_conf['group'])) > 1: raise ConfigError('Only one address-group, network-group or domain-group can be specified') for group in valid_groups: if group in side_conf['group']: group_name = side_conf['group'][group] fw_group = f'ipv6_{group}' if ipv6 and group in ['address_group', 'network_group'] else group error_group = fw_group.replace("_", "-") if group in ['address_group', 'network_group', 'domain_group']: types = [t for t in ['address', 'fqdn', 'geoip'] if t in side_conf] if types: raise ConfigError(f'{error_group} and {types[0]} cannot both be defined') if group_name and group_name[0] == '!': group_name = group_name[1:] group_obj = dict_search_args(firewall, 'group', fw_group, group_name) if group_obj is None: raise ConfigError(f'Invalid {error_group} "{group_name}" on firewall rule') if not group_obj: Warning(f'{error_group} "{group_name}" has no members!') if 'port' in side_conf or dict_search_args(side_conf, 'group', 'port_group'): if 'protocol' not in rule_conf: raise ConfigError('Protocol must be defined if specifying a port or port-group') if rule_conf['protocol'] not in ['tcp', 'udp', 'tcp_udp']: raise ConfigError('Protocol must be tcp, udp, or tcp_udp when specifying a port or port-group') if 'port' in side_conf and dict_search_args(side_conf, 'group', 'port_group'): raise ConfigError(f'{side} port-group and port cannot both be defined') if 'add_address_to_group' in rule_conf: for type in ['destination_address', 'source_address']: if type in rule_conf['add_address_to_group']: if 'address_group' not in rule_conf['add_address_to_group'][type]: raise ConfigError(f'Dynamic address group must be defined.') else: target = rule_conf['add_address_to_group'][type]['address_group'] fwall_group = 'ipv6_address_group' if ipv6 else 'address_group' group_obj = dict_search_args(firewall, 'group', 'dynamic_group', fwall_group, target) if group_obj is None: raise ConfigError(f'Invalid dynamic address group on firewall rule') if 'log_options' in rule_conf: if 'log' not in rule_conf: raise ConfigError('log-options defined, but log is not enable') if 'snapshot_length' in rule_conf['log_options'] and 'group' not in rule_conf['log_options']: raise ConfigError('log-options snapshot-length defined, but log group is not define') if 'queue_threshold' in rule_conf['log_options'] and 'group' not in rule_conf['log_options']: raise ConfigError('log-options queue-threshold defined, but log group is not define') for direction in ['inbound_interface','outbound_interface']: if direction in rule_conf: if 'name' in rule_conf[direction] and 'group' in rule_conf[direction]: raise ConfigError(f'Cannot specify both interface group and interface name for {direction}') if 'group' in rule_conf[direction]: group_name = rule_conf[direction]['group'] if group_name[0] == '!': group_name = group_name[1:] group_obj = dict_search_args(firewall, 'group', 'interface_group', group_name) if group_obj is None: raise ConfigError(f'Invalid interface group "{group_name}" on firewall rule') if not group_obj: Warning(f'interface-group "{group_name}" has no members!') def verify_nested_group(group_name, group, groups, seen): if 'include' not in group: return seen.append(group_name) for g in group['include']: if g not in groups: raise ConfigError(f'Nested group "{g}" does not exist') if g in seen: raise ConfigError(f'Group "{group_name}" has a circular reference') if 'include' in groups[g]: verify_nested_group(g, groups[g], groups, seen) def verify_hardware_offload(ifname): ethtool = Ethtool(ifname) enabled, fixed = ethtool.get_hw_tc_offload() if not enabled and fixed: raise ConfigError(f'Interface "{ifname}" does not support hardware offload') if not enabled: raise ConfigError(f'Interface "{ifname}" requires "offload hw-tc-offload"') def verify(firewall): if 'flowtable' in firewall: for flowtable, flowtable_conf in firewall['flowtable'].items(): if 'interface' not in flowtable_conf: raise ConfigError(f'Flowtable "{flowtable}" requires at least one interface') for ifname in flowtable_conf['interface']: verify_interface_exists(ifname) if dict_search_args(flowtable_conf, 'offload') == 'hardware': interfaces = flowtable_conf['interface'] for ifname in interfaces: verify_hardware_offload(ifname) if 'group' in firewall: for group_type in nested_group_types: if group_type in firewall['group']: groups = firewall['group'][group_type] for group_name, group in groups.items(): verify_nested_group(group_name, group, groups, []) if 'ipv4' in firewall: for name in ['name','forward','input','output', 'prerouting']: if name in firewall['ipv4']: for name_id, name_conf in firewall['ipv4'][name].items(): if 'jump' in name_conf['default_action'] and 'default_jump_target' not in name_conf: raise ConfigError('default-action set to jump, but no default-jump-target specified') if 'default_jump_target' in name_conf: target = name_conf['default_jump_target'] if 'jump' not in name_conf['default_action']: raise ConfigError('default-jump-target defined, but default-action jump needed and it is not defined') if name_conf['default_jump_target'] == name_id: raise ConfigError(f'Loop detected on default-jump-target.') ## Now need to check that default-jump-target exists (other firewall chain/name) if target not in dict_search_args(firewall['ipv4'], 'name'): raise ConfigError(f'Invalid jump-target. Firewall name {target} does not exist on the system') if 'rule' in name_conf: for rule_id, rule_conf in name_conf['rule'].items(): verify_rule(firewall, rule_conf, False) if 'ipv6' in firewall: for name in ['name','forward','input','output', 'prerouting']: if name in firewall['ipv6']: for name_id, name_conf in firewall['ipv6'][name].items(): if 'jump' in name_conf['default_action'] and 'default_jump_target' not in name_conf: raise ConfigError('default-action set to jump, but no default-jump-target specified') if 'default_jump_target' in name_conf: target = name_conf['default_jump_target'] if 'jump' not in name_conf['default_action']: raise ConfigError('default-jump-target defined, but default-action jump needed and it is not defined') if name_conf['default_jump_target'] == name_id: raise ConfigError(f'Loop detected on default-jump-target.') ## Now need to check that default-jump-target exists (other firewall chain/name) if target not in dict_search_args(firewall['ipv6'], 'name'): raise ConfigError(f'Invalid jump-target. Firewall name {target} does not exist on the system') if 'rule' in name_conf: for rule_id, rule_conf in name_conf['rule'].items(): verify_rule(firewall, rule_conf, True) #### ZONESSSS local_zone = False zone_interfaces = [] if 'zone' in firewall: for zone, zone_conf in firewall['zone'].items(): if 'local_zone' not in zone_conf and 'interface' not in zone_conf: raise ConfigError(f'Zone "{zone}" has no interfaces and is not the local zone') if 'local_zone' in zone_conf: if local_zone: raise ConfigError('There cannot be multiple local zones') if 'interface' in zone_conf: raise ConfigError('Local zone cannot have interfaces assigned') if 'intra_zone_filtering' in zone_conf: raise ConfigError('Local zone cannot use intra-zone-filtering') local_zone = True if 'interface' in zone_conf: found_duplicates = [intf for intf in zone_conf['interface'] if intf in zone_interfaces] if found_duplicates: raise ConfigError(f'Interfaces cannot be assigned to multiple zones') zone_interfaces += zone_conf['interface'] if 'intra_zone_filtering' in zone_conf: intra_zone = zone_conf['intra_zone_filtering'] if len(intra_zone) > 1: raise ConfigError('Only one intra-zone-filtering action must be specified') if 'firewall' in intra_zone: v4_name = dict_search_args(intra_zone, 'firewall', 'name') if v4_name and not dict_search_args(firewall, 'ipv4', 'name', v4_name): raise ConfigError(f'Firewall name "{v4_name}" does not exist') v6_name = dict_search_args(intra_zone, 'firewall', 'ipv6_name') if v6_name and not dict_search_args(firewall, 'ipv6', 'name', v6_name): raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist') if not v4_name and not v6_name: raise ConfigError('No firewall names specified for intra-zone-filtering') if 'from' in zone_conf: for from_zone, from_conf in zone_conf['from'].items(): if from_zone not in firewall['zone']: raise ConfigError(f'Zone "{zone}" refers to a non-existent or deleted zone "{from_zone}"') v4_name = dict_search_args(from_conf, 'firewall', 'name') if v4_name and not dict_search_args(firewall, 'ipv4', 'name', v4_name): raise ConfigError(f'Firewall name "{v4_name}" does not exist') v6_name = dict_search_args(from_conf, 'firewall', 'ipv6_name') if v6_name and not dict_search_args(firewall, 'ipv6', 'name', v6_name): raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist') return None def generate(firewall): if not os.path.exists(nftables_conf): firewall['first_install'] = True if 'zone' in firewall: for local_zone, local_zone_conf in firewall['zone'].items(): if 'local_zone' not in local_zone_conf: continue local_zone_conf['from_local'] = {} for zone, zone_conf in firewall['zone'].items(): if zone == local_zone or 'from' not in zone_conf: continue if local_zone in zone_conf['from']: local_zone_conf['from_local'][zone] = zone_conf['from'][local_zone] render(nftables_conf, 'firewall/nftables.j2', firewall) + render(sysctl_file, 'firewall/sysctl-firewall.conf.j2', firewall) return None -def apply_sysfs(firewall): - for name, conf in sysfs_config.items(): - paths = glob(conf['sysfs']) - value = None - - if name in firewall['global_options']: - conf_value = firewall['global_options'][name] - if conf_value in conf: - value = conf[conf_value] - elif conf_value == 'enable': - value = '1' - elif conf_value == 'disable': - value = '0' - - if value: - for path in paths: - with open(path, 'w') as f: - f.write(value) - def apply(firewall): install_result, output = rc_cmd(f'nft --file {nftables_conf}') if install_result == 1: raise ConfigError(f'Failed to apply firewall: {output}') - apply_sysfs(firewall) + # Apply firewall global-options sysctl settings + cmd(f'sysctl -f {sysctl_file}') call_dependents() # T970 Enable a resolver (systemd daemon) that checks # domain-group/fqdn addresses and update entries for domains by timeout # If router loaded without internet connection or for synchronization domain_action = 'stop' if dict_search_args(firewall, 'group', 'domain_group') or firewall['ip_fqdn'] or firewall['ip6_fqdn']: domain_action = 'restart' call(f'systemctl {domain_action} vyos-domain-resolver.service') if firewall['geoip_updated']: # Call helper script to Update set contents if 'name' in firewall['geoip_updated'] or 'ipv6_name' in firewall['geoip_updated']: print('Updating GeoIP. Please wait...') geoip_update(firewall) 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/system_conntrack.py b/src/conf_mode/system_conntrack.py index d9c38fd95..aa290788c 100755 --- a/src/conf_mode/system_conntrack.py +++ b/src/conf_mode/system_conntrack.py @@ -1,256 +1,256 @@ #!/usr/bin/env python3 # # Copyright (C) 2021-2024 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.base import Warning from vyos.config import Config from vyos.configdep import set_dependents, call_dependents from vyos.utils.dict import dict_search from vyos.utils.dict import dict_search_args from vyos.utils.dict import dict_search_recursive from vyos.utils.process import cmd from vyos.utils.process import rc_cmd from vyos.template import render from vyos import ConfigError from vyos import airbag airbag.enable() conntrack_config = r'/etc/modprobe.d/vyatta_nf_conntrack.conf' sysctl_file = r'/run/sysctl/10-vyos-conntrack.conf' nftables_ct_file = r'/run/nftables-ct.conf' # Every ALG (Application Layer Gateway) consists of either a Kernel Object # also called a Kernel Module/Driver or some rules present in iptables module_map = { 'ftp': { 'ko': ['nf_nat_ftp', 'nf_conntrack_ftp'], 'nftables': ['tcp dport {21} ct helper set "ftp_tcp" return'] }, 'h323': { 'ko': ['nf_nat_h323', 'nf_conntrack_h323'], 'nftables': ['udp dport {1719} ct helper set "ras_udp" return', 'tcp dport {1720} ct helper set "q931_tcp" return'] }, 'nfs': { 'nftables': ['tcp dport {111} ct helper set "rpc_tcp" return', 'udp dport {111} ct helper set "rpc_udp" return'] }, 'pptp': { 'ko': ['nf_nat_pptp', 'nf_conntrack_pptp'], 'nftables': ['tcp dport {1723} ct helper set "pptp_tcp" return'], 'ipv4': True }, 'rtsp': { 'ko': ['nf_nat_rtsp', 'nf_conntrack_rtsp'], 'nftables': ['tcp dport {554} ct helper set "rtsp_tcp" return'], 'ipv4': True }, 'sip': { 'ko': ['nf_nat_sip', 'nf_conntrack_sip'], 'nftables': ['tcp dport {5060,5061} ct helper set "sip_tcp" return', 'udp dport {5060,5061} ct helper set "sip_udp" return'] }, 'sqlnet': { 'nftables': ['tcp dport {1521,1525,1536} ct helper set "tns_tcp" return'] }, 'tftp': { 'ko': ['nf_nat_tftp', 'nf_conntrack_tftp'], 'nftables': ['udp dport {69} ct helper set "tftp_udp" return'] }, } valid_groups = [ 'address_group', 'domain_group', 'network_group', 'port_group' ] def get_config(config=None): if config: conf = config else: conf = Config() base = ['system', 'conntrack'] conntrack = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, with_recursive_defaults=True) conntrack['firewall'] = conf.get_config_dict(['firewall'], key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) conntrack['ipv4_nat_action'] = 'accept' if conf.exists(['nat']) else 'return' conntrack['ipv6_nat_action'] = 'accept' if conf.exists(['nat66']) else 'return' conntrack['wlb_action'] = 'accept' if conf.exists(['load-balancing', 'wan']) else 'return' conntrack['wlb_local_action'] = conf.exists(['load-balancing', 'wan', 'enable-local-traffic']) conntrack['module_map'] = module_map if conf.exists(['service', 'conntrack-sync']): set_dependents('conntrack_sync', conf) # If conntrack status changes, VRF zone rules need updating if conf.exists(['vrf']): set_dependents('vrf', conf) return conntrack def verify(conntrack): for inet in ['ipv4', 'ipv6']: if dict_search_args(conntrack, 'ignore', inet, 'rule') != None: for rule, rule_config in conntrack['ignore'][inet]['rule'].items(): if dict_search('destination.port', rule_config) or \ dict_search('destination.group.port_group', rule_config) or \ dict_search('source.port', rule_config) or \ dict_search('source.group.port_group', rule_config): if 'protocol' not in rule_config or rule_config['protocol'] not in ['tcp', 'udp']: raise ConfigError(f'Port requires tcp or udp as protocol in rule {rule}') tcp_flags = dict_search_args(rule_config, 'tcp', 'flags') if tcp_flags: if dict_search_args(rule_config, 'protocol') != 'tcp': raise ConfigError('Protocol must be tcp when specifying tcp flags') not_flags = dict_search_args(rule_config, 'tcp', 'flags', 'not') if not_flags: duplicates = [flag for flag in tcp_flags if flag in not_flags] if duplicates: raise ConfigError(f'Cannot match a tcp flag as set and not set') for side in ['destination', 'source']: if side in rule_config: side_conf = rule_config[side] if 'group' in side_conf: if len({'address_group', 'network_group', 'domain_group'} & set(side_conf['group'])) > 1: raise ConfigError('Only one address-group, network-group or domain-group can be specified') for group in valid_groups: if group in side_conf['group']: group_name = side_conf['group'][group] error_group = group.replace("_", "-") if group in ['address_group', 'network_group', 'domain_group']: if 'address' in side_conf: raise ConfigError(f'{error_group} and address cannot both be defined') if group_name and group_name[0] == '!': group_name = group_name[1:] if inet == 'ipv6': group = f'ipv6_{group}' group_obj = dict_search_args(conntrack['firewall'], 'group', group, group_name) if group_obj is None: raise ConfigError(f'Invalid {error_group} "{group_name}" on ignore rule') if not group_obj: Warning(f'{error_group} "{group_name}" has no members!') - Warning(f'It is prefered to defined {inet} conntrack ignore rules in the <firewall {inet} prerouting raw> section') + Warning(f'It is prefered to define {inet} conntrack ignore rules in <firewall {inet} prerouting raw> section') if dict_search_args(conntrack, 'timeout', 'custom', inet, 'rule') != None: for rule, rule_config in conntrack['timeout']['custom'][inet]['rule'].items(): if 'protocol' not in rule_config: raise ConfigError(f'Conntrack custom timeout rule {rule} requires protocol tcp or udp') else: if 'tcp' in rule_config['protocol'] and 'udp' in rule_config['protocol']: raise ConfigError(f'conntrack custom timeout rule {rule} - Cant use both tcp and udp protocol') return None def generate(conntrack): if not os.path.exists(nftables_ct_file): conntrack['first_install'] = True # Determine if conntrack is needed conntrack['ipv4_firewall_action'] = 'return' conntrack['ipv6_firewall_action'] = 'return' if dict_search_args(conntrack['firewall'], 'global_options', 'state_policy') != None: conntrack['ipv4_firewall_action'] = 'accept' conntrack['ipv6_firewall_action'] = 'accept' else: for rules, path in dict_search_recursive(conntrack['firewall'], 'rule'): if any(('state' in rule_conf or 'connection_status' in rule_conf or 'offload_target' in rule_conf) for rule_conf in rules.values()): if path[0] == 'ipv4': conntrack['ipv4_firewall_action'] = 'accept' elif path[0] == 'ipv6': conntrack['ipv6_firewall_action'] = 'accept' render(conntrack_config, 'conntrack/vyos_nf_conntrack.conf.j2', conntrack) render(sysctl_file, 'conntrack/sysctl.conf.j2', conntrack) render(nftables_ct_file, 'conntrack/nftables-ct.j2', conntrack) return None def apply(conntrack): # Depending on the enable/disable state of the ALG (Application Layer Gateway) # modules we need to either insmod or rmmod the helpers. add_modules = [] rm_modules = [] for module, module_config in module_map.items(): if dict_search_args(conntrack, 'modules', module) is None: if 'ko' in module_config: unloaded = [mod for mod in module_config['ko'] if os.path.exists(f'/sys/module/{mod}')] rm_modules.extend(unloaded) else: if 'ko' in module_config: add_modules.extend(module_config['ko']) # Add modules before nftables uses them if add_modules: module_str = ' '.join(add_modules) cmd(f'modprobe -a {module_str}') # Load new nftables ruleset install_result, output = rc_cmd(f'nft --file {nftables_ct_file}') if install_result == 1: raise ConfigError(f'Failed to apply configuration: {output}') # Remove modules after nftables stops using them if rm_modules: module_str = ' '.join(rm_modules) cmd(f'rmmod {module_str}') try: call_dependents() except ConfigError: # Ignore config errors on dependent due to being called too early. Example: # ConfigError("ConfigError('Interface ethN requires an IP address!')") pass # We silently ignore all errors # See: https://bugzilla.redhat.com/show_bug.cgi?id=1264080 cmd(f'sysctl -f {sysctl_file}') 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/firewall/15-to-16 b/src/migration-scripts/firewall/15-to-16 new file mode 100755 index 000000000..7c8d38fe6 --- /dev/null +++ b/src/migration-scripts/firewall/15-to-16 @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-2024 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/>. + +# T6394: Migrate conntrack timeout options to firewall global-options + # from: set system conntrack timeout .. + # to: set firewall global-options timeout ... + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree + +if len(argv) < 2: + print("Must specify file name!") + exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +firewall_base = ['firewall', 'global-options'] +conntrack_base = ['system', 'conntrack', 'timeout'] +config = ConfigTree(config_file) + +if not config.exists(conntrack_base): + # Nothing to do + exit(0) + +for protocol in ['icmp', 'tcp', 'udp', 'other']: + if config.exists(conntrack_base + [protocol]): + if not config.exists(firewall_base): + config.set(firewall_base + ['timeout']) + config.copy(conntrack_base + [protocol], firewall_base + ['timeout', protocol]) + config.delete(conntrack_base + [protocol]) + +try: + with open(file_name, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print("Failed to save the modified config: {}".format(e)) + exit(1) \ No newline at end of file