diff options
-rw-r--r-- | data/templates/firewall/nftables-nat.tmpl | 139 | ||||
-rw-r--r-- | debian/control | 5 | ||||
-rw-r--r-- | interface-definitions/include/nat-address.xml.i | 37 | ||||
-rw-r--r-- | interface-definitions/include/nat-port.xml.i | 17 | ||||
-rw-r--r-- | interface-definitions/include/nat-rule.xml.i | 303 | ||||
-rw-r--r-- | interface-definitions/include/nat-translation-port.xml.i | 13 | ||||
-rw-r--r-- | interface-definitions/nat.xml.in | 115 | ||||
-rw-r--r-- | op-mode-definitions/nat.xml | 98 | ||||
-rwxr-xr-x | src/conf_mode/nat.py | 259 | ||||
-rwxr-xr-x | src/migration-scripts/nat/4-to-5 | 58 | ||||
-rwxr-xr-x | src/op_mode/show_nat_statistics.py | 63 | ||||
-rwxr-xr-x | src/op_mode/to_be_migrated/vyatta-nat-translations.pl | 267 | ||||
-rwxr-xr-x | src/validators/ip-protocol | 41 | ||||
-rwxr-xr-x | src/validators/ipv4-address-exclude | 7 | ||||
-rwxr-xr-x | src/validators/ipv4-prefix-exclude | 7 | ||||
-rwxr-xr-x | src/validators/ipv4-range | 33 | ||||
-rwxr-xr-x | src/validators/ipv4-range-exclude | 7 |
17 files changed, 1469 insertions, 0 deletions
diff --git a/data/templates/firewall/nftables-nat.tmpl b/data/templates/firewall/nftables-nat.tmpl new file mode 100644 index 000000000..abb32ddc6 --- /dev/null +++ b/data/templates/firewall/nftables-nat.tmpl @@ -0,0 +1,139 @@ +#!/usr/sbin/nft -f + +# Start with clean NAT table +flush table nat + +{% if helper_functions == 'remove' %} +{# NAT if going to be disabled - remove rules and targets from nftables #} + +{% set base_command = "delete rule ip raw" %} +{{ base_command }} PREROUTING handle {{ pre_ct_ignore }} +{{ base_command }} OUTPUT handle {{ out_ct_ignore }} +{{ base_command }} PREROUTING handle {{ pre_ct_conntrack }} +{{ base_command }} OUTPUT handle {{ out_ct_conntrack }} + +delete chain ip raw NAT_CONNTRACK + +{% elif helper_functions == 'add' %} +{# NAT if enabled - add targets to nftables #} +add chain ip raw NAT_CONNTRACK +add rule ip raw NAT_CONNTRACK counter accept + +{% set base_command = "add rule ip raw" %} + +{{ base_command }} PREROUTING position {{ pre_ct_ignore }} counter jump VYATTA_CT_HELPER +{{ base_command }} OUTPUT position {{ out_ct_ignore }} counter jump VYATTA_CT_HELPER +{{ base_command }} PREROUTING position {{ pre_ct_conntrack }} counter jump NAT_CONNTRACK +{{ base_command }} OUTPUT position {{ out_ct_conntrack }} counter jump NAT_CONNTRACK +{% endif %} + +{% macro nat_rule(rule, chain) %} +{% set src_addr = "ip saddr " + rule.source_address if rule.source_address %} +{% set src_port = "sport { " + rule.source_port +" }" if rule.source_port %} +{% set dst_addr = "ip daddr " + rule.dest_address if rule.dest_address %} +{% set dst_port = "dport { " + rule.dest_port +" }" if rule.dest_port %} +{% set comment = "DST-NAT-" + rule.number %} + +{% if chain == "PREROUTING" %} +{% set interface = " iifname \"" + rule.interface_in + "\"" %} +{% set trns_addr = "dnat to " + rule.translation_address %} +{% elif chain == "POSTROUTING" %} +{% set interface = " oifname \"" + rule.interface_out + "\"" %} +{% set trns_addr = rule.translation_address %} +{% if rule.translation_address != 'masquerade' %} +{% set trns_addr = "snat to " + trns_addr %} +{% endif %} +{% endif %} +{% set trns_port = ":" + rule.translation_port if rule.translation_port %} + +{% if rule.protocol == "tcp_udp" %} +{% set protocol = "tcp" %} +{% set comment = comment + " tcp_udp" %} +{% else %} +{% set protocol = rule.protocol %} +{% endif %} + +{% if rule.log %} +{% set base_log = "[NAT-DST-" + rule.number %} +{% if rule.exclude %} +{% set log = base_log + "-EXCL]" %} +{% elif rule.translation_address == 'masquerade' %} +{% set log = base_log + "-MASQ]" %} +{% else %} +{% set log = base_log + "]" %} +{% endif %} +{% endif %} + +{% if rule.exclude %} +{# rule has been marked as "exclude" thus we simply return here #} +{% set trns_addr = "return" %} +{% set trns_port = "" %} +{% endif %} + +{% set output = "add rule ip nat " + chain + interface %} + +{% if protocol != "all" %} +{% set output = output + " ip protocol " + protocol %} +{% endif %} + +{% if src_addr %} +{% set output = output + " " + src_addr %} +{% endif %} +{% if src_port %} +{% set output = output + " " + protocol + " " + src_port %} +{% endif %} + +{% if dst_addr %} +{% set output = output + " " + dst_addr %} +{% endif %} +{% if dst_port %} +{% set output = output + " " + protocol + " " + dst_port %} +{% endif %} + +{# Count packets #} +{% set output = output + " counter" %} + +{# Special handling of log option, we must repeat the entire rule before the #} +{# NAT translation options are added, this is essential #} +{% if log %} +{% set log_output = output + " log prefix \"" + log + "\" comment \"" + comment + "\"" %} +{% endif %} + +{% if trns_addr %} +{% set output = output + " " + trns_addr %} +{% endif %} + +{% if trns_port %} +{# Do not add a whitespace here, translation port must be directly added after IP address #} +{# e.g. 192.0.2.10:3389 #} +{% set output = output + trns_port %} +{% endif %} + +{% if comment %} +{% set output = output + " comment \"" + comment + "\"" %} +{% endif %} + +{{ log_output if log_output }} +{{ output }} + +{# Special handling if protocol is tcp_udp, we must repeat the entire rule with udp as protocol #} +{% if rule.protocol == "tcp_udp" %} +{# Beware of trailing whitespace, without it the comment tcp_udp will be changed to udp_udp #} +{{ log_output | replace("tcp ", "udp ") if log_output }} +{{ output | replace("tcp ", "udp ") }} +{% endif %} +{% endmacro %} + +# +# Destination NAT rules build up here +# +{% for rule in destination if not rule.disabled -%} +{{ nat_rule(rule, 'PREROUTING') }} +{% endfor %} + +# +# Source NAT rules build up here +# +{% for rule in source if not rule.disabled -%} +{{ nat_rule(rule, 'POSTROUTING') }} +{% endfor %} diff --git a/debian/control b/debian/control index ab0fc0b29..bd6e445ee 100644 --- a/debian/control +++ b/debian/control @@ -32,7 +32,9 @@ Depends: python3, python3-waitress, python3-netaddr, python3-zmq, + python3-jmespath, cron, + systemd, easy-rsa, ipaddrcheck, tcpdump, @@ -90,8 +92,11 @@ Depends: python3, pmacct (>= 1.6.0), python3-certbot-nginx, pppoe, + libxml-simple-perl, salt-minion, vyos-utils, + nftables (>= 0.9.3), + conntrack, ${shlibs:Depends}, ${misc:Depends} Description: VyOS configuration scripts and data diff --git a/interface-definitions/include/nat-address.xml.i b/interface-definitions/include/nat-address.xml.i new file mode 100644 index 000000000..933dae07b --- /dev/null +++ b/interface-definitions/include/nat-address.xml.i @@ -0,0 +1,37 @@ +<leafNode name="address"> + <properties> + <help>IP address, subnet, or range</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address to match</description> + </valueHelp> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 prefix to match</description> + </valueHelp> + <valueHelp> + <format>ipv4range</format> + <description>IPv4 address range to match</description> + </valueHelp> + <valueHelp> + <format>!ipv4</format> + <description>Match everything except the specified address</description> + </valueHelp> + <valueHelp> + <format>!ipv4net</format> + <description>Match everything except the specified prefix</description> + </valueHelp> + <valueHelp> + <format>!ipv4range</format> + <description>Match everything except the specified range</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + <validator name="ipv4-prefix"/> + <validator name="ipv4-range"/> + <validator name="ipv4-address-exclude"/> + <validator name="ipv4-prefix-exclude"/> + <validator name="ipv4-range-exclude"/> + </constraint> + </properties> +</leafNode> diff --git a/interface-definitions/include/nat-port.xml.i b/interface-definitions/include/nat-port.xml.i new file mode 100644 index 000000000..24803ae05 --- /dev/null +++ b/interface-definitions/include/nat-port.xml.i @@ -0,0 +1,17 @@ +<leafNode name="port"> + <properties> + <help>Port number</help> + <valueHelp> + <format>1-65535</format> + <description>Numeric IP port</description> + </valueHelp> + <valueHelp> + <format>start-end</format> + <description>Numbered port range (e.g., 1001-1005)</description> + </valueHelp> + <valueHelp> + <format> </format> + <description>\n\nMultiple destination ports can be specified as a comma-separated list.\nThe whole list can also be negated using '!'.\nFor example: '!22,telnet,http,123,1001-1005'</description> + </valueHelp> + </properties> +</leafNode> diff --git a/interface-definitions/include/nat-rule.xml.i b/interface-definitions/include/nat-rule.xml.i new file mode 100644 index 000000000..f62a08987 --- /dev/null +++ b/interface-definitions/include/nat-rule.xml.i @@ -0,0 +1,303 @@ +<tagNode name="rule"> + <properties> + <help>Rule number for NAT</help> + <valueHelp> + <format>1-9999</format> + <description>Number for this NAT rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-9999"/> + </constraint> + <constraintErrorMessage>NAT rule number must be between 1 and 9999</constraintErrorMessage> + </properties> + <children> + <leafNode name="description"> + <properties> + <help>Rule description</help> + </properties> + </leafNode> + <node name="destination"> + <properties> + <help>NAT destination parameters</help> + </properties> + <children> + #include <include/nat-address.xml.i> + #include <include/nat-port.xml.i> + </children> + </node> + <leafNode name="disable"> + <properties> + <help>Disable NAT rule</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="exclude"> + <properties> + <help>Exclude packets matching this rule from NAT</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="log"> + <properties> + <help>NAT rule logging</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="protocol"> + <properties> + <help>Protocol to NAT</help> + <completionHelp> + <list>all ip hopopt icmp igmp ggp ipencap st tcp egp igp pup udp tcp_udp hmp xns-idp rdp iso-tp4 dccp xtp ddp idpr-cmtp ipv6 ipv6-route ipv6-frag idrp rsvp gre esp ah skip ipv6-icmp ipv6-nonxt ipv6-opts rspf vmtp eigrp ospf ax.25 ipip etherip encap 99 pim ipcomp vrrp l2tp isis sctp fc mobility-header udplite mpls-in-ip manet hip shim6 wesp rohc</list> + </completionHelp> + <valueHelp> + <format>all</format> + <description>All IP protocols</description> + </valueHelp> + <valueHelp> + <format>ip</format> + <description>Internet Protocol, pseudo protocol number</description> + </valueHelp> + <valueHelp> + <format>hopopt</format> + <description>IPv6 Hop-by-Hop Option [RFC1883]</description> + </valueHelp> + <valueHelp> + <format>icmp</format> + <description>internet control message protocol</description> + </valueHelp> + <valueHelp> + <format>igmp</format> + <description>Internet Group Management</description> + </valueHelp> + <valueHelp> + <format>ggp</format> + <description>gateway-gateway protocol</description> + </valueHelp> + <valueHelp> + <format>ipencap</format> + <description>IP encapsulated in IP (officially IP)</description> + </valueHelp> + <valueHelp> + <format>st</format> + <description>ST datagram mode</description> + </valueHelp> + <valueHelp> + <format>tcp</format> + <description>transmission control protocol</description> + </valueHelp> + <valueHelp> + <format>egp</format> + <description>exterior gateway protocol</description> + </valueHelp> + <valueHelp> + <format>igp</format> + <description>any private interior gateway (Cisco)</description> + </valueHelp> + <valueHelp> + <format>pup</format> + <description>PARC universal packet protocol</description> + </valueHelp> + <valueHelp> + <format>udp</format> + <description>user datagram protocol</description> + </valueHelp> + <valueHelp> + <format>tcp_udp</format> + <description>Both TCP and UDP</description> + </valueHelp> + <valueHelp> + <format>hmp</format> + <description>host monitoring protocol</description> + </valueHelp> + <valueHelp> + <format>xns-idp</format> + <description>Xerox NS IDP</description> + </valueHelp> + <valueHelp> + <format>rdp</format> + <description>"reliable datagram" protocol</description> + </valueHelp> + <valueHelp> + <format>iso-tp4</format> + <description>ISO Transport Protocol class 4 [RFC905]</description> + </valueHelp> + <valueHelp> + <format>dccp</format> + <description>Datagram Congestion Control Prot. [RFC4340]</description> + </valueHelp> + <valueHelp> + <format>xtp</format> + <description>Xpress Transfer Protocol</description> + </valueHelp> + <valueHelp> + <format>ddp</format> + <description>Datagram Delivery Protocol</description> + </valueHelp> + <valueHelp> + <format>idpr-cmtp</format> + <description>IDPR Control Message Transport</description> + </valueHelp> + <valueHelp> + <format>Ipv6</format> + <description>Internet Protocol, version 6</description> + </valueHelp> + <valueHelp> + <format>ipv6-route</format> + <description>Routing Header for IPv6</description> + </valueHelp> + <valueHelp> + <format>ipv6-frag</format> + <description>Fragment Header for IPv6</description> + </valueHelp> + <valueHelp> + <format>idrp</format> + <description>Inter-Domain Routing Protocol</description> + </valueHelp> + <valueHelp> + <format>rsvp</format> + <description>Reservation Protocol</description> + </valueHelp> + <valueHelp> + <format>gre</format> + <description>General Routing Encapsulation</description> + </valueHelp> + <valueHelp> + <format>esp</format> + <description>Encap Security Payload [RFC2406]</description> + </valueHelp> + <valueHelp> + <format>ah</format> + <description>Authentication Header [RFC2402]</description> + </valueHelp> + <valueHelp> + <format>skip</format> + <description>SKIP</description> + </valueHelp> + <valueHelp> + <format>ipv6-icmp</format> + <description>ICMP for IPv6</description> + </valueHelp> + <valueHelp> + <format>ipv6-nonxt</format> + <description>No Next Header for IPv6</description> + </valueHelp> + <valueHelp> + <format>ipv6-opts</format> + <description>Destination Options for IPv6</description> + </valueHelp> + <valueHelp> + <format>rspf</format> + <description>Radio Shortest Path First (officially CPHB)</description> + </valueHelp> + <valueHelp> + <format>vmtp</format> + <description>Versatile Message Transport</description> + </valueHelp> + <valueHelp> + <format>eigrp</format> + <description>Enhanced Interior Routing Protocol (Cisco)</description> + </valueHelp> + <valueHelp> + <format>ospf</format> + <description>Open Shortest Path First IGP</description> + </valueHelp> + <valueHelp> + <format>ax.25</format> + <description>AX.25 frames</description> + </valueHelp> + <valueHelp> + <format>ipip</format> + <description>IP-within-IP Encapsulation Protocol</description> + </valueHelp> + <valueHelp> + <format>etherip</format> + <description>Ethernet-within-IP Encapsulation [RFC3378]</description> + </valueHelp> + <valueHelp> + <format>encap</format> + <description>Yet Another IP encapsulation [RFC1241]</description> + </valueHelp> + <valueHelp> + <format>99</format> + <description>Any private encryption scheme</description> + </valueHelp> + <valueHelp> + <format>pim</format> + <description>Protocol Independent Multicast</description> + </valueHelp> + <valueHelp> + <format>ipcomp</format> + <description>IP Payload Compression Protocol</description> + </valueHelp> + <valueHelp> + <format>vrrp</format> + <description>Virtual Router Redundancy Protocol [RFC5798]</description> + </valueHelp> + <valueHelp> + <format>l2tp</format> + <description>Layer Two Tunneling Protocol [RFC2661]</description> + </valueHelp> + <valueHelp> + <format>isis</format> + <description>IS-IS over IPv4</description> + </valueHelp> + <valueHelp> + <format>sctp</format> + <description>Stream Control Transmission Protocol</description> + </valueHelp> + <valueHelp> + <format>fc</format> + <description>Fibre Channel</description> + </valueHelp> + <valueHelp> + <format>mobility-header</format> + <description>Mobility Support for IPv6 [RFC3775]</description> + </valueHelp> + <valueHelp> + <format>udplite</format> + <description>UDP-Lite [RFC3828]</description> + </valueHelp> + <valueHelp> + <format>mpls-in-ip</format> + <description>MPLS-in-IP [RFC4023]</description> + </valueHelp> + <valueHelp> + <format>manet</format> + <description>MANET Protocols [RFC5498]</description> + </valueHelp> + <valueHelp> + <format>hip</format> + <description>Host Identity Protocol</description> + </valueHelp> + <valueHelp> + <format>shim6</format> + <description>Shim6 Protocol</description> + </valueHelp> + <valueHelp> + <format>wesp</format> + <description>Wrapped Encapsulating Security Payload</description> + </valueHelp> + <valueHelp> + <format>rohc</format> + <description>Robust Header Compression</description> + </valueHelp> + <valueHelp> + <format>0-255</format> + <description>IP protocol number</description> + </valueHelp> + <constraint> + <validator name="ip-protocol"/> + </constraint> + </properties> + </leafNode> + <node name="source"> + <properties> + <help>NAT source parameters</help> + </properties> + <children> + #include <include/nat-address.xml.i> + #include <include/nat-port.xml.i> + </children> + </node> + </children> +</tagNode> diff --git a/interface-definitions/include/nat-translation-port.xml.i b/interface-definitions/include/nat-translation-port.xml.i new file mode 100644 index 000000000..93de471e3 --- /dev/null +++ b/interface-definitions/include/nat-translation-port.xml.i @@ -0,0 +1,13 @@ +<leafNode name="port"> + <properties> + <help>Port number</help> + <valueHelp> + <format>1-65535</format> + <description>Numeric IP port</description> + </valueHelp> + <valueHelp> + <format><start>-<end></format> + <description>Numbered port range (e.g., 1001-1005)</description> + </valueHelp> + </properties> +</leafNode> diff --git a/interface-definitions/nat.xml.in b/interface-definitions/nat.xml.in new file mode 100644 index 000000000..af9dd1eff --- /dev/null +++ b/interface-definitions/nat.xml.in @@ -0,0 +1,115 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="nat" owner="sudo ${vyos_conf_scripts_dir}/nat.py"> + <properties> + <help>Network Address Translation (NAT) parameters</help> + <priority>220</priority> + </properties> + <children> + <node name="destination"> + <properties> + <help>Destination NAT settings</help> + </properties> + <children> + #include <include/nat-rule.xml.i> + <tagNode name="rule"> + <children> + <leafNode name="inbound-interface"> + <properties> + <help>Inbound interface of NAT traffic</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces.py</script> + </completionHelp> + </properties> + </leafNode> + <node name="translation"> + <properties> + <help>Inside NAT IP (destination NAT only)</help> + </properties> + <children> + <leafNode name="address"> + <properties> + <help>IP address, subnet, or range</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address to match</description> + </valueHelp> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 prefix to match</description> + </valueHelp> + <valueHelp> + <format>ipv4range</format> + <description>IPv4 address range to match</description> + </valueHelp> + <!-- TODO: add general iptables constraint script --> + </properties> + </leafNode> + #include <include/nat-translation-port.xml.i> + </children> + </node> + </children> + </tagNode> + </children> + </node> + <node name="source"> + <properties> + <help>Source NAT settings</help> + </properties> + <children> + #include <include/nat-rule.xml.i> + <tagNode name="rule"> + <children> + <leafNode name="outbound-interface"> + <properties> + <help>Outbound interface of NAT traffic</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces.py</script> + </completionHelp> + </properties> + </leafNode> + <node name="translation"> + <properties> + <help>Outside NAT IP (source NAT only)</help> + </properties> + <children> + <leafNode name="address"> + <properties> + <help>IP address, subnet, or range</help> + <completionHelp> + <list>masquerade</list> + </completionHelp> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address to match</description> + </valueHelp> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 prefix to match</description> + </valueHelp> + <valueHelp> + <format>ipv4range</format> + <description>IPv4 address range to match</description> + </valueHelp> + <valueHelp> + <format>masquerade</format> + <description>NAT to the primary address of outbound-interface</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + <validator name="ipv4-address"/> + <validator name="ipv4-range"/> + <regex>(masquerade)</regex> + </constraint> + </properties> + </leafNode> + #include <include/nat-translation-port.xml.i> + </children> + </node> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/nat.xml b/op-mode-definitions/nat.xml new file mode 100644 index 000000000..ffaa2cba3 --- /dev/null +++ b/op-mode-definitions/nat.xml @@ -0,0 +1,98 @@ +<?xml version="1.0" encoding="UTF-8"?>
+<interfaceDefinition>
+ <node name="show">
+ <children>
+ <node name="nat">
+ <properties>
+ <help>Show Network Address Translation (NAT) information</help>
+ </properties>
+ <children>
+ <node name="source">
+ <properties>
+ <help>Show source Network Address Translation (NAT) information</help>
+ </properties>
+ <children>
+ <node name="rules">
+ <properties>
+ <help>Show configured source NAT rules</help>
+ </properties>
+ <command>echo To be migrated to Python - https://phabricator.vyos.net/T2459</command>
+ </node>
+ <node name="statistics">
+ <properties>
+ <help>Show statistics for configured source NAT rules</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/show_nat_statistics.py --source</command>
+ </node>
+ <node name="translations">
+ <properties>
+ <help>Show active source NAT translations</help>
+ </properties>
+ <children>
+ <tagNode name="address">
+ <properties>
+ <help>Show active source NAT translations for an IP address</help>
+ <completionHelp>
+ <list><x.x.x.x></list>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/to_be_migrated/vyatta-nat-translations.pl --type=source --verbose --ipaddr="$6"</command>
+ </tagNode>
+ <node name="detail">
+ <properties>
+ <help>Show active source NAT translations detail</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/to_be_migrated/vyatta-nat-translations.pl --type=source --verbose</command>
+ </node>
+ </children>
+ <command>${vyos_op_scripts_dir}/to_be_migrated/vyatta-nat-translations.pl --type=source</command>
+ </node>
+ </children>
+ </node>
+ <node name="destination">
+ <properties>
+ <help>Show destination Network Address Translation (NAT) information</help>
+ </properties>
+ <children>
+ <node name="rules">
+ <properties>
+ <help>Show configured destination NAT rules</help>
+ </properties>
+ <command>echo To be migrated to Python - https://phabricator.vyos.net/T2459</command>
+ </node>
+ <node name="statistics">
+ <properties>
+ <help>Show statistics for configured destination NAT rules</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/show_nat_statistics.py --destination</command>
+ </node>
+ <node name="translations">
+ <properties>
+ <help>Show active destination NAT translations</help>
+ </properties>
+ <children>
+ <tagNode name="address">
+ <properties>
+ <help>Show active NAT destination translations for an IP address</help>
+ <completionHelp>
+ <list><x.x.x.x></list>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/to_be_migrated/vyatta-nat-translations.pl --type=destination --verbose --ipaddr="$6"</command>
+ </tagNode>
+ <node name="detail">
+ <properties>
+ <help>Show active destination NAT translations detail</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/to_be_migrated/vyatta-nat-translations.pl --type=destination --verbose</command>
+ </node>
+ </children>
+ <command>${vyos_op_scripts_dir}/to_be_migrated/vyatta-nat-translations.pl --type=destination</command>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py new file mode 100755 index 000000000..5cb1af1f1 --- /dev/null +++ b/src/conf_mode/nat.py @@ -0,0 +1,259 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020 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 jmespath +import json +import os + +from copy import deepcopy +from sys import exit +from netifaces import interfaces + +from vyos.config import Config +from vyos.template import render +from vyos.util import call, cmd +from vyos.validate import is_addr_assigned +from vyos import ConfigError + +default_config_data = { + 'deleted': False, + 'destination': [], + 'helper_functions': None, + 'pre_ct_helper': '', + 'pre_ct_conntrack': '', + 'out_ct_helper': '', + 'out_ct_conntrack': '', + 'source': [] +} + +iptables_nat_config = '/tmp/vyos-nat-rules.nft' + +def _check_kmod(): + """ load required Kernel modules """ + modules = ['nft_nat', 'nft_chain_nat_ipv4'] + for module in modules: + if not os.path.exists(f'/sys/module/{module}'): + if call(f'modprobe {module}') != 0: + raise ConfigError(f'Loading Kernel module {module} failed') + + +def get_handler(json, chain, target): + """ Get nftable rule handler number of given chain/target combination. + Handler is required when adding NAT/Conntrack helper targets """ + for x in json: + if x['chain'] != chain: + continue + if x['target'] != target: + continue + return x['handle'] + + return None + + +def verify_rule(rule, err_msg): + """ Common verify steps used for both source and destination NAT """ + if rule['translation_port'] or rule['dest_port'] or rule['source_port']: + if rule['protocol'] not in ['tcp', 'udp', 'tcp_udp']: + proto = rule['protocol'] + raise ConfigError(f'{err_msg} ports can only be specified when protocol is "tcp", "udp" or "tcp_udp" (currently "{proto}")') + + if '/' in rule['translation_address']: + raise ConfigError(f'{err_msg}\n' \ + 'Cannot use ports with an IPv4net type translation address as it\n' \ + 'statically maps a whole network of addresses onto another\n' \ + 'network of addresses') + + if not rule['translation_address']: + raise ConfigError(f'{err_msg} translation address not specified') + else: + addr = rule['translation_address'] + if addr != 'masquerade' and not is_addr_assigned(addr): + print(f'Warning: IP address {addr} does not exist on the system!') + + +def parse_source_destination(conf, source_dest): + """ Common wrapper to read in both NAT source and destination CLI """ + tmp = [] + base_level = ['nat', source_dest] + conf.set_level(base_level) + for number in conf.list_nodes(['rule']): + rule = { + 'description': '', + 'dest_address': '', + 'dest_port': '', + 'disabled': False, + 'exclude': False, + 'interface_in': '', + 'interface_out': '', + 'log': False, + 'protocol': 'all', + 'number': number, + 'source_address': '', + 'source_port': '', + 'translation_address': '', + 'translation_port': '' + } + conf.set_level(base_level + ['rule', number]) + + if conf.exists(['description']): + rule['description'] = conf.return_value(['description']) + + if conf.exists(['destination', 'address']): + rule['dest_address'] = conf.return_value(['destination', 'address']) + + if conf.exists(['destination', 'port']): + rule['dest_port'] = conf.return_value(['destination', 'port']) + + if conf.exists(['disable']): + rule['disabled'] = True + + if conf.exists(['exclude']): + rule['exclude'] = True + + if conf.exists(['inbound-interface']): + rule['interface_in'] = conf.return_value(['inbound-interface']) + + if conf.exists(['outbound-interface']): + rule['interface_out'] = conf.return_value(['outbound-interface']) + + if conf.exists(['log']): + rule['log'] = True + + if conf.exists(['protocol']): + rule['protocol'] = conf.return_value(['protocol']) + + if conf.exists(['source', 'address']): + rule['source_address'] = conf.return_value(['source', 'address']) + + if conf.exists(['source', 'port']): + rule['source_port'] = conf.return_value(['source', 'port']) + + if conf.exists(['translation', 'address']): + rule['translation_address'] = conf.return_value(['translation', 'address']) + + if conf.exists(['translation', 'port']): + rule['translation_port'] = conf.return_value(['translation', 'port']) + + tmp.append(rule) + + return tmp + +def get_config(): + nat = deepcopy(default_config_data) + conf = Config() + + # read in current nftable (once) for further processing + tmp = cmd('nft -j list table raw') + nftable_json = json.loads(tmp) + + # condense the full JSON table into a list with only relevand informations + pattern = 'nftables[?rule].rule[?expr[].jump].{chain: chain, handle: handle, target: expr[].jump.target | [0]}' + condensed_json = jmespath.search(pattern, nftable_json) + + if not conf.exists(['nat']): + nat['helper_functions'] = 'remove' + + # Retrieve current table handler positions + nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYATTA_CT_HELPER') + nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK') + nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYATTA_CT_HELPER') + nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'NAT_CONNTRACK') + + nat['deleted'] = True + + return nat + + # check if NAT connection tracking helpers need to be set up - this has to + # be done only once + if not get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK'): + nat['helper_functions'] = 'add' + + # Retrieve current table handler positions + nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYATTA_CT_IGNORE') + nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'VYATTA_CT_PREROUTING_HOOK') + nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYATTA_CT_IGNORE') + nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'VYATTA_CT_OUTPUT_HOOK') + + # set config level for parsing in NAT configuration + conf.set_level(['nat']) + + # use a common wrapper function to read in the source / destination + # tree from the config - thus we do not need to replicate almost the + # same code :-) + for tgt in ['source', 'destination']: + nat[tgt] = parse_source_destination(conf, tgt) + + return nat + +def verify(nat): + if nat['deleted']: + # no need to verify the CLI as NAT is going to be deactivated + return None + + if nat['helper_functions']: + if not (nat['pre_ct_ignore'] or nat['pre_ct_conntrack'] or nat['out_ct_ignore'] or nat['out_ct_conntrack']): + raise Exception('could not determine nftable ruleset handlers') + + for rule in nat['source']: + interface = rule['interface_out'] + err_msg = f"Source NAT configuration error in rule {rule['number']}:" + + if interface and interface not in interfaces(): + print(f'NAT configuration warning: interface {interface} does not exist on this system') + + if not rule['interface_out']: + raise ConfigError(f'{err_msg} outbound-interface not specified') + + # common rule verification + verify_rule(rule, err_msg) + + for rule in nat['destination']: + interface = rule['interface_in'] + err_msg = f"Destination NAT configuration error in rule {rule['number']}:" + + if interface and interface not in interfaces(): + print(f'NAT configuration warning: interface {interface} does not exist on this system') + + if not rule['interface_in']: + raise ConfigError(f'{err_msg} inbound-interface not specified') + + # common rule verification + verify_rule(rule, err_msg) + + return None + +def generate(nat): + render(iptables_nat_config, 'firewall/nftables-nat.tmpl', nat, trim_blocks=True, permission=0o755) + + return None + +def apply(nat): + cmd(f'{iptables_nat_config}') + if os.path.isfile(iptables_nat_config): + os.unlink(iptables_nat_config) + + return None + +if __name__ == '__main__': + try: + _check_kmod() + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/migration-scripts/nat/4-to-5 b/src/migration-scripts/nat/4-to-5 new file mode 100755 index 000000000..dda191719 --- /dev/null +++ b/src/migration-scripts/nat/4-to-5 @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020 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/>. + +# Drop the enable/disable from the nat "log" node. If log node is specified +# it is "enabled" + +from sys import argv,exit +from vyos.configtree import ConfigTree + +if (len(argv) < 1): + print("Must specify file name!") + exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +config = ConfigTree(config_file) + +if not config.exists(['nat']): + # Nothing to do + exit(0) +else: + for direction in ['source', 'destination']: + if not config.exists(['nat', direction]): + continue + + for rule in config.list_nodes(['nat', direction, 'rule']): + base = ['nat', direction, 'rule', rule] + + # Check if the log node exists and if log is enabled, + # migrate it to the new valueless 'log' node + if config.exists(base + ['log']): + tmp = config.return_value(base + ['log']) + config.delete(base + ['log']) + if tmp == 'enable': + config.set(base + ['log']) + + 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) diff --git a/src/op_mode/show_nat_statistics.py b/src/op_mode/show_nat_statistics.py new file mode 100755 index 000000000..0b53112f2 --- /dev/null +++ b/src/op_mode/show_nat_statistics.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018 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 jmespath +import json + +from argparse import ArgumentParser +from jinja2 import Template +from sys import exit +from vyos.util import cmd + +OUT_TMPL_SRC=""" +rule pkts bytes interface +---- ---- ----- --------- +{% for r in output %} +{%- if r.comment -%} +{%- set packets = r.counter.packets -%} +{%- set bytes = r.counter.bytes -%} +{%- set interface = r.interface -%} +{# remove rule comment prefix #} +{%- set comment = r.comment | replace('SRC-NAT-', '') | replace('DST-NAT-', '') | replace(' tcp_udp', '') -%} +{{ "%-4s" | format(comment) }} {{ "%9s" | format(packets) }} {{ "%12s" | format(bytes) }} {{ interface }} +{%- endif %} +{% endfor %} +""" + +parser = ArgumentParser() +group = parser.add_mutually_exclusive_group() +group.add_argument("--source", help="Show statistics for configured source NAT rules", action="store_true") +group.add_argument("--destination", help="Show statistics for configured destination NAT rules", action="store_true") +args = parser.parse_args() + +if args.source or args.destination: + tmp = cmd('sudo nft -j list table nat') + tmp = json.loads(tmp) + + source = r"nftables[?rule.chain=='POSTROUTING'].rule.{chain: chain, handle: handle, comment: comment, counter: expr[].counter | [0], interface: expr[].match.right | [0] }" + destination = r"nftables[?rule.chain=='PREROUTING'].rule.{chain: chain, handle: handle, comment: comment, counter: expr[].counter | [0], interface: expr[].match.right | [0] }" + data = { + 'output' : jmespath.search(source if args.source else destination, tmp), + 'direction' : 'source' if args.source else 'destination' + } + + tmpl = Template(OUT_TMPL_SRC, lstrip_blocks=True) + print(tmpl.render(data)) + exit(0) +else: + parser.print_help() + exit(1) + diff --git a/src/op_mode/to_be_migrated/vyatta-nat-translations.pl b/src/op_mode/to_be_migrated/vyatta-nat-translations.pl new file mode 100755 index 000000000..94ed74bad --- /dev/null +++ b/src/op_mode/to_be_migrated/vyatta-nat-translations.pl @@ -0,0 +1,267 @@ +#!/usr/bin/perl +# +# Module: vyatta-nat-translate.pl +# +# **** License **** +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 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. +# +# This code was originally developed by Vyatta, Inc. +# Portions created by Vyatta are Copyright (C) 2007 Vyatta, Inc. +# All Rights Reserved. +# +# Author: Stig Thormodsrud +# Date: July 2008 +# Description: Script to display nat translations +# +# **** End License **** +# + +use Getopt::Long; +use XML::Simple; +use Data::Dumper; +use POSIX; + +use warnings; +use strict; + +my $dump = 0; +my ($xml_file, $verbose, $proto, $stats, $ipaddr, $pipe); +my $type; +my $verbose_format = "%-20s %-18s %-20s %-18s\n"; +my $format = "%-20s %-20s %-4s %-8s"; + +sub add_xml_root { + my $xml = shift; + + $xml = '<data>' . $xml . '</data>'; + return $xml; +} + + +sub read_xml_file { + my $file = shift; + + local($/, *FD); # slurp mode + open FD, "<", $file or die "Couldn't open $file\n"; + my $xml = <FD>; + close FD; + return $xml; +} + +sub print_xml { + my $data = shift; + print Dumper($data); +} + +sub guess_snat_dnat { + my ($src, $dst) = @_; + + if ($src->{original} eq $dst->{reply}) { + return "dnat"; + } + if ($dst->{original} eq $src->{reply}) { + return "snat"; + } + return "unkn"; +} + +sub nat_print_xml { + my ($data, $type) = @_; + + my $flow = 0; + + my %flowh; + while (1) { + my $meta = 0; + last if ! defined $data->{flow}[$flow]; + my $flow_ref = $data->{flow}[$flow]; + my $flow_type = $flow_ref->{type}; + my (%src, %dst, %sport, %dport, %proto); + my (%packets, %bytes); + my $timeout = undef; + my $uses = undef; + while (1) { + my $meta_ref = $flow_ref->{meta}[$meta]; + last if ! defined $meta_ref; + my $dir = $meta_ref->{direction}; + if ($dir eq 'original' or $dir eq 'reply') { + my $l3_ref = $meta_ref->{layer3}[0]; + my $l4_ref = $meta_ref->{layer4}[0]; + my $count_ref = $meta_ref->{counters}[0]; + if (defined $l3_ref) { + $src{$dir} = $l3_ref->{src}[0]; + $dst{$dir} = $l3_ref->{dst}[0]; + if (defined $l4_ref) { + $sport{$dir} = $l4_ref->{sport}[0]; + $dport{$dir} = $l4_ref->{dport}[0]; + $proto{$dir} = $l4_ref->{protoname}; + } + } + if (defined $stats and defined $count_ref) { + $packets{$dir} = $count_ref->{packets}[0]; + $bytes{$dir} = $count_ref->{bytes}[0]; + } + } elsif ($dir eq 'independent') { + $timeout = $meta_ref->{timeout}[0]; + $uses = $meta_ref->{'use'}[0]; + } + $meta++; + } + my ($proto, $in_src, $in_dst, $out_src, $out_dst); + $proto = $proto{original}; + $in_src = "$src{original}"; + $in_src .= ":$sport{original}" if defined $sport{original}; + $in_dst = "$dst{original}"; + $in_dst .= ":$dport{original}" if defined $dport{original}; + $out_src = "$dst{reply}"; + $out_src .= ":$dport{reply}" if defined $dport{reply}; + $out_dst = "$src{reply}"; + $out_dst .= ":$sport{reply}" if defined $sport{reply}; + if (defined $verbose) { + printf($verbose_format, $in_src, $in_dst, $out_src, $out_dst); + } +# if (! defined $type) { +# $type = guess_snat_dnat(\%src, \%dst); +# } + if (defined $type) { + my ($from, $to); + if ($type eq 'source') { + $from = "$src{original}"; + $to = "$dst{reply}"; + if (defined $sport{original} and defined $dport{reply}) { + if ($sport{original} ne $dport{reply}) { + $from .= ":$sport{original}"; + $to .= ":$dport{reply}"; + } + } + } else { + $from = "$dst{original}"; + $to = "$src{reply}"; + if (defined $dport{original} and defined $sport{reply}) { + if ($dport{original} ne $sport{reply}) { + $from .= ":$dport{original}"; + $to .= ":$sport{reply}"; + } + } + } + if (defined $verbose) { + print " $proto: $from ==> $to"; + } else { + my $timeout2 = ""; + if (defined $timeout) { + $timeout2 = $timeout; + } + printf($format, $from, $to, $proto, $timeout2); + print " $flow_type" if defined $flow_type; + print "\n"; + } + } + if (defined $verbose) { + print " timeout: $timeout" if defined $timeout; + print " use: $uses " if defined $uses; + print " type: $flow_type" if defined $flow_type; + print "\n"; + } + if (defined $stats) { + foreach my $dir ('original', 'reply') { + if (defined $packets{$dir}) { + printf(" %-8s: packets %s, bytes %s\n", + $dir, $packets{$dir}, $bytes{$dir}); + } + } + } + $flow++; + } + return $flow; +} + + +# +# main +# +GetOptions("verbose" => \$verbose, + "proto=s" => \$proto, + "file=s" => \$xml_file, + "stats" => \$stats, + "type=s" => \$type, + "ipaddr=s" => \$ipaddr, + "pipe" => \$pipe, +); + +my $conntrack = '/usr/sbin/conntrack'; +if (! -f $conntrack) { + die "Package [conntrack] not installed"; +} + +die "Must specify NAT type!" if !defined($type); +die "Unknown NAT type!" if (($type ne 'source') && ($type ne 'destination')); + +my $xs = XML::Simple->new(ForceArray => 1, KeepRoot => 0); +my ($xml, $data); + +# flush stdout after every write for pipe mode +$| = 1 if defined $pipe; + +if (defined $verbose) { + printf($verbose_format, 'Pre-NAT src', 'Pre-NAT dst', + 'Post-NAT src', 'Post-NAT dst'); +} else { + printf($format, 'Pre-NAT', 'Post-NAT', 'Prot', 'Timeout'); + print " Type" if defined $pipe; + print "\n"; +} + +if (defined $xml_file) { + $xml = read_xml_file($xml_file); + $data = $xs->XMLin($xml); + if ($dump) { + print_xml($data); + exit; + } + nat_print_xml($data, 'snat'); + +} elsif (defined $pipe) { + while ($xml = <STDIN>) { + $xml =~ s/\<\?xml version=\"1\.0\" encoding=\"utf-8\"\?\>//; + $xml =~ s/\<conntrack\>//; + $xml = add_xml_root($xml); + $data = $xs->XMLin($xml); + nat_print_xml($data, $type); + } +} else { + if (defined $proto) { + $proto = "-p $proto" + } else { + $proto = ""; + } + if ($type eq 'source') { + my $ipopt = ""; + if (defined $ipaddr) { + $ipopt = "--orig-src $ipaddr"; + } + $xml = `sudo $conntrack -L -n $ipopt -o xml $proto 2>/dev/null`; + chomp $xml; + $data = undef; + $data = $xs->XMLin($xml) if ! $xml eq ''; + } + if ($type eq 'destination') { + my $ipopt = ""; + if (defined $ipaddr) { + $ipopt = "--orig-dst $ipaddr"; + } + $xml = `sudo $conntrack -L -g $ipopt -o xml $proto 2>/dev/null`; + chomp $xml; + $data = undef; + $data = $xs->XMLin($xml) if ! $xml eq ''; + } + nat_print_xml($data, $type) if defined $data; +} + +# end of file diff --git a/src/validators/ip-protocol b/src/validators/ip-protocol new file mode 100755 index 000000000..078f8e319 --- /dev/null +++ b/src/validators/ip-protocol @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020 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 re +from sys import argv,exit + +if __name__ == '__main__': + if len(argv) != 2: + exit(1) + + input = argv[1] + try: + # IP protocol can be in the range 0 - 255, thus the range must end with 256 + if int(input) in range(0, 256): + exit(0) + except ValueError: + pass + + pattern = "!?\\b(all|ip|hopopt|icmp|igmp|ggp|ipencap|st|tcp|egp|igp|pup|udp|" \ + "tcp_udp|hmp|xns-idp|rdp|iso-tp4|dccp|xtp|ddp|idpr-cmtp|ipv6|" \ + "ipv6-route|ipv6-frag|idrp|rsvp|gre|esp|ah|skip|ipv6-icmp|" \ + "ipv6-nonxt|ipv6-opts|rspf|vmtp|eigrp|ospf|ax.25|ipip|etherip|" \ + "encap|99|pim|ipcomp|vrrp|l2tp|isis|sctp|fc|mobility-header|" \ + "udplite|mpls-in-ip|manet|hip|shim6|wesp|rohc)\\b" + if re.match(pattern, input): + exit(0) + + exit(1) diff --git a/src/validators/ipv4-address-exclude b/src/validators/ipv4-address-exclude new file mode 100755 index 000000000..80ad17d45 --- /dev/null +++ b/src/validators/ipv4-address-exclude @@ -0,0 +1,7 @@ +#!/bin/sh +arg="$1" +if [ "${arg:0:1}" != "!" ]; then + exit 1 +fi +path=$(dirname "$0") +${path}/ipv4-address "${arg:1}" diff --git a/src/validators/ipv4-prefix-exclude b/src/validators/ipv4-prefix-exclude new file mode 100755 index 000000000..4f7de400a --- /dev/null +++ b/src/validators/ipv4-prefix-exclude @@ -0,0 +1,7 @@ +#!/bin/sh +arg="$1" +if [ "${arg:0:1}" != "!" ]; then + exit 1 +fi +path=$(dirname "$0") +${path}/ipv4-prefix "${arg:1}" diff --git a/src/validators/ipv4-range b/src/validators/ipv4-range new file mode 100755 index 000000000..ae3f3f163 --- /dev/null +++ b/src/validators/ipv4-range @@ -0,0 +1,33 @@ +#!/bin/bash + +# snippet from https://stackoverflow.com/questions/10768160/ip-address-converter +ip2dec () { + local a b c d ip=$@ + IFS=. read -r a b c d <<< "$ip" + printf '%d\n' "$((a * 256 ** 3 + b * 256 ** 2 + c * 256 + d))" +} + +# Only run this if there is a hypen present in $1 +if [[ "$1" =~ "-" ]]; then + # This only works with real bash (<<<) - split IP addresses into array with + # hyphen as delimiter + readarray -d - -t strarr <<< $1 + + ipaddrcheck --is-ipv4-single ${strarr[0]} + if [ $? -gt 0 ]; then + exit 1 + fi + + ipaddrcheck --is-ipv4-single ${strarr[1]} + if [ $? -gt 0 ]; then + exit 1 + fi + + start=$(ip2dec ${strarr[0]}) + stop=$(ip2dec ${strarr[1]}) + if [ $start -ge $stop ]; then + exit 1 + fi +fi + +exit 0 diff --git a/src/validators/ipv4-range-exclude b/src/validators/ipv4-range-exclude new file mode 100755 index 000000000..3787b4dec --- /dev/null +++ b/src/validators/ipv4-range-exclude @@ -0,0 +1,7 @@ +#!/bin/sh +arg="$1" +if [ "${arg:0:1}" != "!" ]; then + exit 1 +fi +path=$(dirname "$0") +${path}/ipv4-range "${arg:1}" |