diff options
| author | Kyrylo Yatsenko <hedrok@gmail.com> | 2025-08-27 17:49:12 +0300 |
|---|---|---|
| committer | Kyrylo Yatsenko <hedrok@gmail.com> | 2025-09-16 13:00:56 +0300 |
| commit | e992fb4ec3d65674c794bdcb961b62485fec8d50 (patch) | |
| tree | 37431490c991e47d5cea14acf566c200c131f7fd /src | |
| parent | d72d15c28f2e890ded3e5d24fceac00dac1238ed (diff) | |
| download | vyos-1x-e992fb4ec3d65674c794bdcb961b62485fec8d50.tar.gz vyos-1x-e992fb4ec3d65674c794bdcb961b62485fec8d50.zip | |
T75: migrate from pmacct to ipt_NETFLOW
* Change nft to iptables in system_flow-accounting.py as ipt_NETFLOW
is iptales plugin
* Remove specific and non-relevant pmacct options
* Add ipt_NETFLOW options
* Move 'interfaces' to 'netflow' tree
* Support more flexible 'source-address' and 'source-interface' for
each server instead of one source
* Add migration script
* Update op mode command 'show flow-accounting'
* Update op mode command 'restart flow-accounting'
Diffstat (limited to 'src')
| -rwxr-xr-x | src/conf_mode/system_flow-accounting.py | 273 | ||||
| -rw-r--r-- | src/migration-scripts/flow-accounting/2-to-3 | 65 | ||||
| -rwxr-xr-x | src/op_mode/flow_accounting_op.py | 225 |
3 files changed, 311 insertions, 252 deletions
diff --git a/src/conf_mode/system_flow-accounting.py b/src/conf_mode/system_flow-accounting.py index 618227fc0..b23ee77fd 100755 --- a/src/conf_mode/system_flow-accounting.py +++ b/src/conf_mode/system_flow-accounting.py @@ -17,6 +17,7 @@ import os import re +from ipaddress import ip_interface from sys import exit from vyos.config import Config @@ -24,119 +25,19 @@ 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.process import call -from vyos.utils.process import cmd -from vyos.utils.process import run +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() -uacctd_conf_path = '/run/pmacct/uacctd.conf' -systemd_service = 'uacctd.service' -systemd_override = f'/run/systemd/system/{systemd_service}.d/override.conf' -nftables_nflog_table = 'raw' -nftables_nflog_chain = 'VYOS_PREROUTING_HOOK' -egress_nftables_nflog_table = 'inet mangle' -egress_nftables_nflog_chain = 'FORWARD' - -# get nftables rule dict for chain in table -def _nftables_get_nflog(chain, table): - # define list with rules - rules = [] - - # prepare regex for parsing rules - rule_pattern = '[io]ifname "(?P<interface>[\w\.\*\-]+)".*handle (?P<handle>[\d]+)' - rule_re = re.compile(rule_pattern) - - # run nftables, save output and split it by lines - nftables_command = f'nft -a list chain {table} {chain}' - tmp = cmd(nftables_command, message='Failed to get flows list') - # parse each line and add information to list - for current_rule in tmp.splitlines(): - if 'FLOW_ACCOUNTING_RULE' not in current_rule: - continue - current_rule_parsed = rule_re.search(current_rule) - if current_rule_parsed: - groups = current_rule_parsed.groupdict() - rules.append({ 'interface': groups["interface"], 'table': table, 'handle': groups["handle"] }) - - # return list with rules - return rules - -def _nftables_config(configured_ifaces, direction, length=None): - # define list of nftables commands to modify settings - nftable_commands = [] - nftables_chain = nftables_nflog_chain - nftables_table = nftables_nflog_table - - if direction == "egress": - nftables_chain = egress_nftables_nflog_chain - nftables_table = egress_nftables_nflog_table - - # 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 nftables rules - active_nflog_rules = _nftables_get_nflog(nftables_chain, nftables_table) - - # compare current active list with configured one and delete excessive interfaces, add missed - active_nflog_ifaces = [] - for rule in active_nflog_rules: - interface = rule['interface'] - if interface not in configured_ifaces: - table = rule['table'] - handle = rule['handle'] - nftable_commands.append(f'nft delete rule {table} {nftables_chain} handle {handle}') - else: - active_nflog_ifaces.append({ - 'iface': interface, - }) - - # do not create new rules for already configured interfaces - for iface in active_nflog_ifaces: - if iface in active_nflog_ifaces and 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_prefix = "o" if direction == "egress" else "i" - rule_definition = f'{iface_prefix}ifname "{iface}" counter log group 2 snaplen {length} queue-threshold 100 comment "FLOW_ACCOUNTING_RULE"' - nftable_commands.append(f'nft insert rule {nftables_table} {nftables_chain} {rule_definition}') - # Also add IPv6 ingres logging - if nftables_table == nftables_nflog_table: - nftable_commands.append(f'nft insert rule ip6 {nftables_table} {nftables_chain} {rule_definition}') - - # change nftables - for command in nftable_commands: - cmd(command, raising=ConfigError) - - -def _nftables_trigger_setup(operation: str) -> None: - """Add a dummy rule to unlock the main pmacct loop with a packet-trigger - - Args: - operation (str): 'add' or 'delete' a trigger - """ - # check if a chain exists - table_exists = False - if run('nft -snj list table ip pmacct') == 0: - table_exists = True - - if operation == 'delete' and table_exists: - nft_cmd: str = 'nft delete table ip pmacct' - cmd(nft_cmd, raising=ConfigError) - if operation == 'add' and not table_exists: - nft_cmds: list[str] = [ - 'nft add table ip pmacct', - 'nft add chain ip pmacct pmacct_out { type filter hook output priority raw - 50 \\; policy accept \\; }', - 'nft add rule ip pmacct pmacct_out oif lo ip daddr 127.0.254.0 counter log group 2 snaplen 1 queue-threshold 0 comment NFLOG_TRIGGER' - ] - for nft_cmd in nft_cmds: - cmd(nft_cmd, raising=ConfigError) +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): @@ -166,104 +67,128 @@ def get_config(config=None): return flow_accounting + def verify(flow_config): if not flow_config: return None - # check if collector is enabled - if 'netflow' not in flow_config and 'disable_imt' in flow_config: - raise ConfigError('You need to configure NetFlow, ' \ - 'or not set "disable-imt" for flow-accounting!') - # Check if at least one interface is configured - if 'interface' not in flow_config: + 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['interface']: + 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 NetFlow configuration - if 'netflow' in flow_config: - # check if vrf is defined for netflow - netflow_vrf = None - if 'vrf' in flow_config: - netflow_vrf = flow_config['vrf'] - - # check if at least one NetFlow collector is configured if NetFlow configuration is presented - if 'server' not in flow_config['netflow']: - raise ConfigError('You need to configure at least one NetFlow server!') - - # Check if configured netflow source-address exist in the system - if 'source_address' in flow_config['netflow']: - if not is_addr_assigned(flow_config['netflow']['source_address'], netflow_vrf): - tmp = flow_config['netflow']['source_address'] - raise ConfigError(f'Configured "netflow source-address {tmp}" does not exist on the system!') - - # 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}"!') + # 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 - render(uacctd_conf_path, 'pmacct/uacctd.conf.j2', flow_config) - render(systemd_override, 'pmacct/override.conf.j2', flow_config) - # Reload systemd manager configuration - call('systemctl daemon-reload') + prev_config = read_file(ipt_netflow_conf_path, defaultonfailure='') -def apply(flow_config): - # Check if flow-accounting was removed and define command - if not flow_config: - _nftables_config([], 'ingress') - _nftables_config([], 'egress') + render(ipt_netflow_conf_path, 'ipt-netflow/ipt_NETFLOW.conf.j2', flow_config) + + new_config = read_file(ipt_netflow_conf_path, defaultonfailure='') - # Stop flow-accounting daemon and remove configuration file - call(f'systemctl stop {systemd_service}') - if os.path.exists(uacctd_conf_path): - os.unlink(uacctd_conf_path) + global need_reload + need_reload = prev_config != new_config - # must be done after systemctl - _nftables_trigger_setup('delete') +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 - # Start/reload flow-accounting daemon - call(f'systemctl restart {systemd_service}') + ingress_interfaces = [] + egress_interfaces = [] - # configure nftables rules for defined interfaces - if 'interface' in flow_config: - _nftables_config(flow_config['interface'], 'ingress', flow_config['packet_length']) + # 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: - _nftables_config(flow_config['interface'], 'egress', flow_config['packet_length']) - else: - _nftables_config([], 'egress') + egress_interfaces = ingress_interfaces - # add a trigger for signal processing - _nftables_trigger_setup('add') + if need_reload: + ipt_netflow.start(ingress_interfaces, egress_interfaces) + else: + ipt_netflow.set_watched_iptables_interfaces( + ingress_interfaces, egress_interfaces + ) if __name__ == '__main__': diff --git a/src/migration-scripts/flow-accounting/2-to-3 b/src/migration-scripts/flow-accounting/2-to-3 new file mode 100644 index 000000000..48292fc1d --- /dev/null +++ b/src/migration-scripts/flow-accounting/2-to-3 @@ -0,0 +1,65 @@ +# 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/>. + +# migrate from pmacct to ipt-NETFLOW: +# Remove 'timeout' subtree, 'buffer-size', 'disable-imt', 'packet-length' and +# 'syslog-facility' from 'system flow-accounting' +# +# Move "system flow-accounting interface" to "system flow-accounting netflow interface" +# +# Remove "system flow-accounting source" and add it to each server +# under "system flow-accounting netflow server SERVER source-address" + + +from vyos.configtree import ConfigTree + +base = ['system', 'flow-accounting'] +remove_keys = [ + ['buffer-size'], + ['disable-imt'], + ['netflow', 'timeout'], + ['packet-length'], + ['syslog-facility'], +] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + # Remove not needed pmacct fields + for k in remove_keys: + p = base + k + if config.exists(p): + config.delete(p) + + # Move "system flow-accounting interface" -> "system flow-accounting netflow interface" + if config.exists(base + ['interface']): + config.copy(base + ['interface'], base + ['netflow', 'interface']) + config.delete(base + ['interface']) + + # Remove old "source-address", add it to each server as "source-address" + source_prev_path = base + ['netflow', 'source-address'] + if config.exists(source_prev_path): + source_value = config.return_value(source_prev_path) + config.delete(source_prev_path) + + # So we loose 'source-address' value if there are no servers + # configured, but it is not valid configuration if there + # is no server, so it shouldn't be a problem + if config.exists(base + ['netflow', 'server']): + path = base + ['netflow', 'server'] + for server in config.list_nodes(path): + config.set(path + [server, 'source-address'], source_value) diff --git a/src/op_mode/flow_accounting_op.py b/src/op_mode/flow_accounting_op.py index f8aabc1ee..0c3184fc1 100755 --- a/src/op_mode/flow_accounting_op.py +++ b/src/op_mode/flow_accounting_op.py @@ -18,18 +18,16 @@ import sys import argparse import re import ipaddress -import os.path from tabulate import tabulate -from json import loads -from vyos.utils.commit import commit_in_progress +from vyos.utils.kernel import is_module_loaded from vyos.utils.process import cmd -from vyos.utils.process import run from vyos.logger import syslog +from vyos.configquery import ConfigTreeQuery +from vyos import ipt_netflow # some default values -uacctd_pidfile = '/var/run/uacctd.pid' -uacctd_pipefile = '/tmp/uacctd.pipe' +flows_dump_path = '/proc/net/stat/ipt_netflow_flows' def parse_port(port): try: @@ -45,7 +43,7 @@ def parse_ports(arg): if re.match(r'^\d+$', arg): # Single port port = parse_port(arg) - return {"type": "single", "value": port} + return {"type": "single", "values": (port,)} elif re.match(r'^\d+\-\d+$', arg): # Port range ports = arg.split("-") @@ -53,12 +51,12 @@ def parse_ports(arg): if ports[0] > ports[1]: raise ValueError("Malformed port range \'{0}\': lower end is greater than the higher".format(arg)) else: - return {"type": "range", "value": (ports[0], ports[1])} + return {"type": "range", "values": range(ports[0], ports[1] + 1)} elif re.match(r'^\d+,.*\d$', arg): # Port list - ports = re.split(r',+', arg) # This allows duplicate commad like '1,,2,3,4' + ports = re.split(r',+', arg) # This allows duplicate commas like '1,,2,3,4' ports = list(map(parse_port, ports)) - return {"type": "list", "value": ports} + return {"type": "list", "values": ports} else: raise ValueError("Malformed port spec \'{0}\'".format(arg)) @@ -69,9 +67,8 @@ def check_host(host): raise ValueError("Invalid host \'{}\', must be a valid IP or IPv6 address".format(host)) # check if flow-accounting running -def _uacctd_running(): - command = 'systemctl status uacctd.service > /dev/null' - return run(command) == 0 +def _netflow_running(): + return is_module_loaded(ipt_netflow.module_name) # get list of interfaces @@ -95,20 +92,56 @@ def _get_ifaces_dict(): # get list of flows def _get_flows_list(): - # run command to get flows list - out = cmd(f'/usr/bin/pmacct -s -O json -T flows -p {uacctd_pipefile}', - message='Failed to get flows list') + # File format: + # When MAC disabled: + # # hash a dev:i,o proto src:ip,port dst:ip,port nexthop tos,tcpflags,options,tcpoptions packets bytes ts:first,last + # 1 c06c 0 4,-1 1 10.2.0.7,0 10.1.0.5,0 0.0.0.0 0,0,0,0 186 15624 92261,131 + # 2 1e3ca 0 3,-1 1 10.1.0.5,0 10.2.0.7,2048 0.0.0.0 0,0,0,0 186 15624 92261,132 + + # When MAC enabled + VLAN fix: + # hash a dev:i,o mac:src,dst vlan type proto src:ip,port dst:ip,port nexthop tos,tcpflags,options,tcpoptions packets bytes ts:first,last + # 1 11a41 0 4,-1 0c:27:1f:55:00:00,0c:e8:b1:71:00:02 - 0800 1 10.2.0.7,0 10.1.0.5,0 0.0.0.0 0,0,0,0 1182 99288 591502,529 + # 2 13bc5 0 4,-1 0c:27:1f:55:00:00,0c:e8:b1:71:00:02 - 0800 1 10.2.0.7,0 10.2.0.1,2048 0.0.0.0 0,0,0,0 577 48468 590831,1006 + # 3 166dd 0 3,-1 0c:f1:0a:d5:00:00,0c:e8:b1:71:00:01 - 0800 1 10.1.0.5,0 10.2.0.7,2048 0.0.0.0 0,0,0,0 1182 99288 591502,529 - # read output - flows_out = out.splitlines() - # make a list with flows flows_list = [] - for flow_line in flows_out: - try: - flows_list.append(loads(flow_line)) - except Exception as err: - syslog.error('Unable to read flow info: {}'.format(err)) + with open(flows_dump_path) as f: + headers = f.readline() + headers = headers.split() + for i, h in enumerate(headers): + + if ',' in h and ':' not in h: + h = 'extra:' + h + + if ':' in h: + key, subkeys = h.split(':', 1) + headers[i] = {'key': key, 'subkeys': subkeys.split(',')} + + linenum = 1 + for flow_line in f: + linenum += 1 + flow_dict = {} + flow_line = flow_line.split() + if len(flow_line) != len(headers): + syslog.error( + f'Unexpected number of elements in {flows_dump_path}, line {linenum}' + ) + continue + for i, val in enumerate(flow_line): + if isinstance(headers[i], str): + flow_dict[headers[i]] = val + elif isinstance(headers[i], dict): + val = val.split(',') + if len(val) != len(headers[i]['subkeys']): + syslog.error( + f"Unexpected number of elements in {flows_dump_path} in column {headers[i]['key']} in line {linenum}" + ) + continue + flow_dict[headers[i]['key']] = dict(zip(headers[i]['subkeys'], val)) + else: + assert False, "Unexpected type of header" + flows_list.append(flow_dict) # return list of flows return flows_list @@ -119,12 +152,15 @@ def _flows_filter(flows, ifaces): # predefine filtered flows list flows_filtered = [] + def _iface_to_str(iface): + if int(iface) in ifaces: + return ifaces[int(iface)] + return 'unknown' + # add interface names to flows for flow in flows: - if flow['iface_in'] in ifaces: - flow['iface_in_name'] = ifaces[flow['iface_in']] - else: - flow['iface_in_name'] = 'unknown' + flow['iface_in_name'] = _iface_to_str(flow['dev']['i']) + flow['iface_out_name'] = _iface_to_str(flow['dev']['o']) # iterate through flows list for flow in flows: @@ -134,16 +170,19 @@ def _flows_filter(flows, ifaces): continue # filter by host if cmd_args.host: - if flow['ip_src'] != cmd_args.host and flow['ip_dst'] != cmd_args.host: + if ( + flow['src']['ip'] != cmd_args.host + and flow['dst']['ip'] != cmd_args.host + ): continue # filter by ports if cmd_args.ports: - if cmd_args.ports['type'] == 'single': - if flow['port_src'] != cmd_args.ports['value'] and flow['port_dst'] != cmd_args.ports['value']: - continue - else: - if flow['port_src'] not in cmd_args.ports['value'] and flow['port_dst'] not in cmd_args.ports['value']: - continue + # for 'single' it is a tuple with one value, for 'list' - list of ports, for range - range of ports + if ( + int(flow['src']['port']) not in cmd_args.ports['values'] + and int(flow['dst']['port']) not in cmd_args.ports['values'] + ): + continue # add filtered flows to new list flows_filtered.append(flow) @@ -159,23 +198,36 @@ def _flows_filter(flows, ifaces): # print flow table def _flows_table_print(flows): # define headers and body - table_headers = ['IN_IFACE', 'SRC_MAC', 'DST_MAC', 'SRC_IP', 'DST_IP', 'SRC_PORT', 'DST_PORT', 'PROTOCOL', 'TOS', 'PACKETS', 'FLOWS', 'BYTES'] + table_headers = [ + 'IN_IFACE', + 'SRC_MAC', + 'DST_MAC', + 'SRC_IP', + 'DST_IP', + 'SRC_PORT', + 'DST_PORT', + 'PROTOCOL', + 'TOS', + 'PACKETS', + # 'FLOWS', # What was here in pmacct? + 'BYTES', + ] table_body = [] # convert flows to list for flow in flows: table_line = [ flow.get('iface_in_name'), - flow.get('mac_src'), - flow.get('mac_dst'), - flow.get('ip_src'), - flow.get('ip_dst'), - flow.get('port_src'), - flow.get('port_dst'), - flow.get('ip_proto'), - flow.get('tos'), + flow.get('mac', {}).get('src'), + flow.get('mac', {}).get('dst'), + flow.get('src', {}).get('ip'), + flow.get('dst', {}).get('ip'), + flow.get('src', {}).get('port'), + flow.get('dst', {}).get('port'), + flow.get('proto'), + flow.get('extra', {}).get('tos'), flow.get('packets'), - flow.get('flows'), - flow.get('bytes') + # flow.get('flows'), + flow.get('bytes'), ] table_body.append(table_line) # configure and fill table @@ -190,21 +242,37 @@ def _flows_table_print(flows): sys.exit(0) -# check if in-memory table is active -def _check_imt(): - if not os.path.exists(uacctd_pipefile): - print("In-memory table is not available") - sys.exit(1) - - # define program arguments cmd_args_parser = argparse.ArgumentParser(description='show flow-accounting') -cmd_args_parser.add_argument('--action', choices=['show', 'clear', 'restart'], required=True, help='command to flow-accounting daemon') -cmd_args_parser.add_argument('--filter', choices=['interface', 'host', 'ports', 'top'], required=False, nargs='*', help='filter flows to display') -cmd_args_parser.add_argument('--interface', required=False, help='interface name for output filtration') -cmd_args_parser.add_argument('--host', type=str, required=False, help='host address for output filtering') -cmd_args_parser.add_argument('--ports', type=str, required=False, help='port number, range or list for output filtering') -cmd_args_parser.add_argument('--top', type=int, required=False, help='top records for output filtering') +# 'clear' and 'restart' are not implemented +cmd_args_parser.add_argument( + '--action', + choices=['show', 'restart'], + default='show', + help='show stat or restart module', +) +cmd_args_parser.add_argument( + '--filter', + choices=['interface', 'host', 'ports', 'top'], + required=False, + nargs='*', + help='filter flows to display', +) +cmd_args_parser.add_argument( + '--interface', required=False, help='interface name for output filtration' +) +cmd_args_parser.add_argument( + '--host', type=str, required=False, help='host address for output filtering' +) +cmd_args_parser.add_argument( + '--ports', + type=str, + required=False, + help='port number, range or list for output filtering', +) +cmd_args_parser.add_argument( + '--top', type=int, required=False, help='top records for output filtering' +) # parse arguments cmd_args = cmd_args_parser.parse_args() @@ -219,30 +287,13 @@ except ValueError as e: sys.exit(1) # main logic -# do nothing if uacctd daemon is not running -if not _uacctd_running(): +# do nothing if ipt_NETFLOW is not active +if not _netflow_running(): print("flow-accounting is not active") sys.exit(1) -# restart pmacct daemon -if cmd_args.action == 'restart': - if commit_in_progress(): - print('Cannot restart flow-accounting while a commit is in progress') - exit(1) - # run command to restart flow-accounting - cmd('systemctl restart uacctd.service', - message='Failed to restart flow-accounting') - -# clear in-memory collected flows -if cmd_args.action == 'clear': - _check_imt() - # run command to clear flows - cmd(f'/usr/bin/pmacct -e -p {uacctd_pipefile}', - message='Failed to clear flows') - # show table with flows if cmd_args.action == 'show': - _check_imt() # get interfaces index and names ifaces_dict = _get_ifaces_dict() # get flows @@ -254,4 +305,22 @@ if cmd_args.action == 'show': # print flows _flows_table_print(tabledata) +if cmd_args.action == 'restart': + ipt_netflow.stop() + + # get needed interfaces + conf = ConfigTreeQuery() + config_path = ['system', 'flow-accounting'] + if not conf.exists(config_path + ['netflow', 'interface']): + print("Flow accounting not configured, exiting") + sys.exit(1) + + ingress_interfaces = conf.values(config_path + ['netflow', 'interface']) + if conf.exists(config_path + ['enable-egress']): + egress_interfaces = ingress_interfaces + else: + egress_interfaces = [] + + ipt_netflow.start(ingress_interfaces, egress_interfaces) + sys.exit(0) |
