Page Menu
Home
VyOS Platform
Search
Configure Global Search
Log In
Files
F118309130
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
14 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
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)
Attached To
Mode
rVYOSONEX vyos-1x
Attached
Detach File
Event Timeline
Log In to Comment