diff options
author | Christian Poessinger <christian@poessinger.com> | 2020-05-14 18:37:39 +0200 |
---|---|---|
committer | Christian Poessinger <christian@poessinger.com> | 2020-05-16 18:25:58 +0200 |
commit | f233a9e4073f3c5257923962a96af5833734e41e (patch) | |
tree | f075caa0bce5b3629fdb5b6e1335f1f2e8fe1839 | |
parent | 1e7d01e5b5a12c5bfaa8989ae6073679f6b647b0 (diff) | |
download | vyos-1x-f233a9e4073f3c5257923962a96af5833734e41e.tar.gz vyos-1x-f233a9e4073f3c5257923962a96af5833734e41e.zip |
nat: T2198: migrate "show nat" commands to XML and Python
- "show nat source|destination statistics" is now implemented in Python
- "show nat source|destination rules" needs a new implementation, see T2459
- "show nat source|destination translations" has been copied over from the old
repo and is here until it is rewritten, this was not possible for "rules"
as there would have been too much dependencies. This one only requires
libxml-simple-perl
-rw-r--r-- | debian/control | 1 | ||||
-rw-r--r-- | op-mode-definitions/nat.xml | 98 | ||||
-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 |
4 files changed, 429 insertions, 0 deletions
diff --git a/debian/control b/debian/control index 323b8130f..f7285941a 100644 --- a/debian/control +++ b/debian/control @@ -91,6 +91,7 @@ Depends: python3, pmacct (>= 1.6.0), python3-certbot-nginx, pppoe, + libxml-simple-perl, salt-minion, vyos-utils, nftables (>= 0.9.3), 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/op_mode/show_nat_statistics.py b/src/op_mode/show_nat_statistics.py new file mode 100755 index 000000000..b77693e19 --- /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-', '') -%} +{{ "%-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 |