Page MenuHomeVyOS Platform

No OneTemporary

Size
14 KB
Referenced Files
None
Subscribers
None
diff --git a/python/vyos/ipt_netflow.py b/python/vyos/ipt_netflow.py
index b1630f30c..14d2a458d 100644
--- a/python/vyos/ipt_netflow.py
+++ b/python/vyos/ipt_netflow.py
@@ -1,174 +1,178 @@
# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
# Package to stop/start ipt_NETFLOW kernel module
# Provides functions stop(), start() and set_watched_iptables_interfaces()
from vyos.utils.kernel import check_kmod
from vyos.utils.kernel import unload_kmod
from vyos.utils.process import cmd
from vyos import ConfigError
module_name = 'ipt_NETFLOW'
iptables_ingress_netflow_table = 'raw'
iptables_ingress_netflow_chain = 'PREROUTING'
iptables_egress_netflow_table = 'mangle'
iptables_egress_netflow_chain = 'POSTROUTING'
# get iptables rule dict for chain in table
def _iptables_get_rules(command, chain, table):
# define list with rules
rules = []
# run iptables, save output and split it by lines
iptables_command = f'{command} -vn -t {table} -L {chain}'
tmp = cmd(iptables_command, message='Failed to get flows list')
lines = tmp.splitlines()
# Sample output to parse:
# vyos@vyos:~$ sudo iptables -vn -t raw -L PREROUTING
# Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
# pkts bytes target prot opt in out source destination
# 0 0 NETFLOW 0 -- eth0 * 0.0.0.0/0 0.0.0.0/0 NETFLOW
# Check that format is as expected
if len(lines) < 2:
raise ConfigError(f'Unexpected output from {command}, too few lines')
if not lines[0].startswith(f'Chain {chain}'):
raise ConfigError(f'Unexpected first line in output of {command}: "{lines[0]}"')
columns = lines[1].split()
# parse each line and add information to list
rulenum = 0
for current_rule in lines[2:]:
rulenum += 1
current_rule_parsed = current_rule.split()
current_rule_parsed = {
columns[i]: current_rule_parsed[i]
for i in range(min(len(current_rule_parsed), len(columns)))
}
if current_rule_parsed.get('target', '') != 'NETFLOW':
continue
rules.append(
{
'interface-in': current_rule_parsed.get("in", ''),
'interface-out': current_rule_parsed.get("out", ''),
'table': table,
'rulenum': rulenum,
}
)
# return list with rules
return rules
def _iptables_config(command, configured_ifaces, direction):
# define list of nftables commands to modify settings
iptables_commands = []
if direction == "ingress":
iptables_table = iptables_ingress_netflow_table
iptables_chain = iptables_ingress_netflow_chain
elif direction == "egress":
iptables_table = iptables_egress_netflow_table
iptables_chain = iptables_egress_netflow_chain
else:
raise ConfigError(f'_iptables_config: Unexpected direction="{direction}"')
# prepare extended list with configured interfaces
configured_ifaces_extended = []
for iface in configured_ifaces:
configured_ifaces_extended.append({'iface': iface})
# get currently configured interfaces with iptables rules
active_rules = _iptables_get_rules(command, iptables_chain, iptables_table)
# compare current active list with configured one and delete excessive interfaces, add missed
active_ifaces = []
interface_key = 'interface-out' if direction == "egress" else "interface-in"
rulenums_delete = []
for rule in active_rules:
interface = rule[interface_key]
if interface not in configured_ifaces:
rulenums_delete.append(rule['rulenum'])
else:
active_ifaces.append({'iface': interface})
# It is important to delete rule with bigger rulenum first, so that other
# rulenums are not changed
rulenums_delete.sort(reverse=True)
for rulenum in rulenums_delete:
iptables_commands.append(
f'{command} -t {iptables_table} -D {iptables_chain} {rulenum}'
)
# do not create new rules for already configured interfaces
for iface in active_ifaces:
if iface in configured_ifaces_extended:
configured_ifaces_extended.remove(iface)
# create missed rules
for iface_extended in configured_ifaces_extended:
iface = iface_extended['iface']
iface_option = "o" if direction == "egress" else "i"
# iptables -t raw -A PREROUTING -j NETFLOW -i eth0
rule_definition = f'{command} -t {iptables_table} -A {iptables_chain} -j NETFLOW -{iface_option} {iface}'
iptables_commands.append(rule_definition)
# change iptables
for command in iptables_commands:
cmd(command, raising=ConfigError)
def _iptables_config_v4_and_v6(configured_ifaces, direction):
for command in 'iptables', 'ip6tables':
_iptables_config(command, configured_ifaces, direction)
-def set_watched_iptables_interfaces(ingress_interfaces, egress_interfaces):
+def set_watched_iptables_interfaces(ingress_interfaces, egress_interfaces, ipv6=True):
"""
Update iptables and ip6tables rules so that ipt_NETFLOW watches
exact list of interfaces in ingress_interfaces for ingress table/chain
and egress_interfaces for egress table/chain
"""
- _iptables_config_v4_and_v6(ingress_interfaces, 'ingress')
- _iptables_config_v4_and_v6(egress_interfaces, 'egress')
+ commands = ['iptables']
+ if ipv6:
+ commands.append('ip6tables')
+ for command in commands:
+ _iptables_config(command, ingress_interfaces, 'ingress')
+ _iptables_config(command, egress_interfaces, 'egress')
def stop():
"""
Stop ipt_NETFLOW: remove all iptables rules that use it
and remove module
"""
set_watched_iptables_interfaces([], [])
unload_kmod(module_name)
-def start(ingress_interfaces, egress_interfaces):
+def start(ingress_interfaces, egress_interfaces, ipv6=True):
"""
Start ipt_NETFLOW:
* Load ipt_NETFLOW kernel module
* Install iptables and ip6tables rules for
ingress_interfaces and egress_interfaces
"""
check_kmod(module_name)
- set_watched_iptables_interfaces(ingress_interfaces, egress_interfaces)
+ set_watched_iptables_interfaces(ingress_interfaces, egress_interfaces, ipv6=ipv6)
diff --git a/src/conf_mode/system_flow-accounting.py b/src/conf_mode/system_flow-accounting.py
index b23ee77fd..3318ad465 100755
--- a/src/conf_mode/system_flow-accounting.py
+++ b/src/conf_mode/system_flow-accounting.py
@@ -1,202 +1,203 @@
#!/usr/bin/env python3
#
# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# 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 ipaddress import ip_interface
from sys import exit
from vyos.config import Config
from vyos.config import config_dict_merge
from vyos.configverify import verify_vrf
from vyos.configverify import verify_interface_exists
from vyos.template import render
from vyos.utils.file import read_file
from vyos.utils.network import is_addr_assigned
from vyos import ConfigError
from vyos import airbag
from vyos import ipt_netflow
airbag.enable()
ipt_netflow_conf_path = '/etc/modprobe.d/ipt_NETFLOW.conf'
# Variable to store between generate and apply
# whether module configuration was changed
# and module reload is needed
need_reload = True
def get_config(config=None):
if config:
conf = config
else:
conf = Config()
base = ['system', 'flow-accounting']
if not conf.exists(base):
return None
flow_accounting = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
# We have gathered the dict representation of the CLI, but there are
# default values which we need to conditionally update into the
# dictionary retrieved.
default_values = conf.get_config_defaults(**flow_accounting.kwargs,
recursive=True)
# delete individual flow type defaults - should only be added if user
# sets this feature
flow_type = 'netflow'
if flow_type not in flow_accounting and flow_type in default_values:
del default_values[flow_type]
flow_accounting = config_dict_merge(default_values, flow_accounting)
return flow_accounting
def verify(flow_config):
if not flow_config:
return None
# Check if at least one interface is configured
if 'netflow' not in flow_config or 'interface' not in flow_config['netflow']:
raise ConfigError('Flow accounting requires at least one interface to ' \
'be configured!')
# check that all configured interfaces exists in the system
for interface in flow_config['netflow']['interface']:
verify_interface_exists(flow_config, interface, warning_only=True)
# check if at least one NetFlow collector is configured
if 'server' not in flow_config['netflow']:
raise ConfigError('You need to configure at least one NetFlow server!')
verify_vrf(flow_config)
# check if vrf is defined for netflow
netflow_vrf = None
if 'vrf' in flow_config:
netflow_vrf = flow_config['vrf']
# Check if configured netflow server source-address exist in the system
# Check if configured netflow server source-address matches protocol of server
# Check if configured netflow server source-interface exists
for server, data in flow_config['netflow']['server'].items():
if 'source_address' in data and 'source_interface' in data:
raise ConfigError(
f'Configured "netflow server {server}" cannot have both "source-address" and "source-interface" fields'
)
if 'source_address' in data:
if not is_addr_assigned(data['source_address'], netflow_vrf):
raise ConfigError(
f'Configured "netflow server {server} source-address {data["source_address"]}" does not exist on the system!'
)
if (
ip_interface(server).version
!= ip_interface(data['source_address']).version
):
raise ConfigError(
f'Configured "netflow server {server} source-address {data["source_address"]}" protocol doesn\'t match server protocol'
)
if 'source_interface' in data:
verify_interface_exists(
flow_config, data['source_interface'], warning_only=True
)
# Check if engine-id compatible with selected protocol version
if 'engine_id' in flow_config['netflow']:
v5_filter = '^(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]):(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])$'
v9v10_filter = '^(\d|[1-9]\d{1,8}|[1-3]\d{9}|4[01]\d{8}|42[0-8]\d{7}|429[0-3]\d{6}|4294[0-8]\d{5}|42949[0-5]\d{4}|429496[0-6]\d{3}|4294967[01]\d{2}|42949672[0-8]\d|429496729[0-5])$'
engine_id = flow_config['netflow']['engine_id']
version = flow_config['netflow']['version']
if flow_config['netflow']['version'] == '5':
regex_filter = re.compile(v5_filter)
if not regex_filter.search(engine_id):
raise ConfigError(
f'You cannot use NetFlow engine-id "{engine_id}" '
f'together with NetFlow protocol version "{version}"!'
)
else:
regex_filter = re.compile(v9v10_filter)
if not regex_filter.search(flow_config['netflow']['engine_id']):
raise ConfigError(
f'Can not use NetFlow engine-id "{engine_id}" together '
f'with NetFlow protocol version "{version}"!'
)
# return True if all checks were passed
return True
def generate(flow_config):
if not flow_config:
if os.path.exists(ipt_netflow_conf_path):
os.unlink(ipt_netflow_conf_path)
return None
prev_config = read_file(ipt_netflow_conf_path, defaultonfailure='')
render(ipt_netflow_conf_path, 'ipt-netflow/ipt_NETFLOW.conf.j2', flow_config)
new_config = read_file(ipt_netflow_conf_path, defaultonfailure='')
global need_reload
need_reload = prev_config != new_config
def apply(flow_config):
# When reloading module we need to first remove
# all iptables usage of ipt_NETFLOW
# When flow_config is disabled everything should be cleaned-up too
if need_reload or not flow_config:
ipt_netflow.stop()
if not flow_config:
if os.path.exists(ipt_netflow_conf_path):
os.unlink(ipt_netflow_conf_path)
return
ingress_interfaces = []
egress_interfaces = []
# configure iptables for defined interfaces
if 'interface' in flow_config['netflow']:
ingress_interfaces = flow_config['netflow']['interface']
# configure egress the same way if configured otherwise remove it
if 'enable_egress' in flow_config:
egress_interfaces = ingress_interfaces
+ enable_ipv6 = flow_config['netflow']['version'] != '5'
if need_reload:
- ipt_netflow.start(ingress_interfaces, egress_interfaces)
+ ipt_netflow.start(ingress_interfaces, egress_interfaces, ipv6=enable_ipv6)
else:
ipt_netflow.set_watched_iptables_interfaces(
- ingress_interfaces, egress_interfaces
+ ingress_interfaces, egress_interfaces, ipv6=enable_ipv6
)
if __name__ == '__main__':
try:
config = get_config()
verify(config)
generate(config)
apply(config)
except ConfigError as e:
print(e)
exit(1)

File Metadata

Mime Type
text/x-diff
Expires
Sat, Oct 17, 12:44 AM (3 h, 40 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4416696
Default Alt Text
(14 KB)

Event Timeline