diff options
Diffstat (limited to 'src')
-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 |
9 files changed, 742 insertions, 0 deletions
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}" |