Skip to content

Commit b04a0f7

Browse files
committed
T75: fixup: Add restart command, refactor for that
1 parent 315f547 commit b04a0f7

4 files changed

Lines changed: 212 additions & 131 deletions

File tree

op-mode-definitions/flow-accounting-op.xml.in

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,14 @@
5151
</node>
5252
</children>
5353
</node>
54+
<node name="restart">
55+
<children>
56+
<leafNode name="flow-accounting">
57+
<properties>
58+
<help>Restart (net)flow accounting process</help>
59+
</properties>
60+
<command>${vyos_op_scripts_dir}/flow_accounting_op.py --action restart</command>
61+
</leafNode>
62+
</children>
63+
</node>
5464
</interfaceDefinition>

python/vyos/ipt_netflow.py

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
2+
#
3+
# This library is free software; you can redistribute it and/or
4+
# modify it under the terms of the GNU Lesser General Public
5+
# License as published by the Free Software Foundation; either
6+
# version 2.1 of the License, or (at your option) any later version.
7+
#
8+
# This library is distributed in the hope that it will be useful,
9+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+
# Lesser General Public License for more details.
12+
#
13+
# You should have received a copy of the GNU Lesser General Public
14+
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
15+
16+
# Package to stop/start ipt_NETFLOW kernel module
17+
18+
# Provides functions stop(), start() and set_watched_iptables_interfaces()
19+
20+
from vyos.utils.kernel import check_kmod
21+
from vyos.utils.kernel import unload_kmod
22+
from vyos.utils.process import cmd
23+
from vyos import ConfigError
24+
25+
module_name = 'ipt_NETFLOW'
26+
iptables_ingress_netflow_table = 'raw'
27+
iptables_ingress_netflow_chain = 'PREROUTING'
28+
iptables_egress_netflow_table = 'mangle'
29+
iptables_egress_netflow_chain = 'POSTROUTING'
30+
31+
# get iptables rule dict for chain in table
32+
def _iptables_get_rules(command, chain, table):
33+
# define list with rules
34+
rules = []
35+
36+
# run iptables, save output and split it by lines
37+
iptables_command = f'{command} -vn -t {table} -L {chain}'
38+
tmp = cmd(iptables_command, message='Failed to get flows list')
39+
lines = tmp.splitlines()
40+
41+
# Sample output to parse:
42+
#vyos@vyos:~$ sudo iptables -vn -t raw -L PREROUTING
43+
#Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
44+
# pkts bytes target prot opt in out source destination
45+
# 0 0 NETFLOW 0 -- eth0 * 0.0.0.0/0 0.0.0.0/0 NETFLOW
46+
47+
# Check that format is as expected
48+
if len(lines) < 2:
49+
raise ConfigError(f'Unexpected output from {command}, too few lines')
50+
if not lines[0].startswith(f'Chain {chain}'):
51+
raise ConfigError(f'Unexpected first line in output of {command}: "{lines[0]}"')
52+
columns = lines[1].split();
53+
54+
# parse each line and add information to list
55+
rulenum = 0
56+
for current_rule in lines[2:]:
57+
rulenum += 1
58+
current_rule_parsed = current_rule.split()
59+
current_rule_parsed = {
60+
columns[i]: current_rule_parsed[i]
61+
for i in range(min(len(current_rule_parsed), len(columns)))
62+
}
63+
if current_rule_parsed.get('target', '') != 'NETFLOW':
64+
continue
65+
66+
rules.append({
67+
'interface-in': current_rule_parsed.get("in", ''),
68+
'interface-out': current_rule_parsed.get("out", ''),
69+
'table': table,
70+
'rulenum': rulenum
71+
})
72+
73+
# return list with rules
74+
return rules
75+
76+
def _iptables_config(command, configured_ifaces, direction):
77+
# define list of nftables commands to modify settings
78+
iptables_commands = []
79+
80+
if direction == "ingress":
81+
iptables_table = iptables_ingress_netflow_table
82+
iptables_chain = iptables_ingress_netflow_chain
83+
elif direction == "egress":
84+
iptables_table = iptables_egress_netflow_table
85+
iptables_chain = iptables_egress_netflow_chain
86+
else:
87+
raise ConfigError(f'_iptables_config: Unexpected direction="{direction}"')
88+
89+
# prepare extended list with configured interfaces
90+
configured_ifaces_extended = []
91+
for iface in configured_ifaces:
92+
configured_ifaces_extended.append({ 'iface': iface })
93+
94+
# get currently configured interfaces with iptables rules
95+
active_rules = _iptables_get_rules(command, iptables_chain, iptables_table)
96+
97+
# compare current active list with configured one and delete excessive interfaces, add missed
98+
active_ifaces = []
99+
interface_key = 'interface-out' if direction == "egress" else "interface-in"
100+
rulenums_delete = []
101+
for rule in active_rules:
102+
interface = rule[interface_key]
103+
if interface not in configured_ifaces:
104+
rulenums_delete.append(rule['rulenum'])
105+
else:
106+
active_ifaces.append({
107+
'iface': interface,
108+
})
109+
110+
# It is important to delete rule with bigger rulenum first, so that other
111+
# rulenums are not changed
112+
rulenums_delete.sort(reverse=True)
113+
for rulenum in rulenums_delete:
114+
iptables_commands.append(f'{command} -t {iptables_table} -D {iptables_chain} {rulenum}')
115+
116+
# do not create new rules for already configured interfaces
117+
for iface in active_ifaces:
118+
if iface in configured_ifaces_extended:
119+
configured_ifaces_extended.remove(iface)
120+
121+
# create missed rules
122+
for iface_extended in configured_ifaces_extended:
123+
iface = iface_extended['iface']
124+
iface_option = "o" if direction == "egress" else "i"
125+
#iptables -t raw -A PREROUTING -j NETFLOW -i eth0
126+
rule_definition = f'{command} -t {iptables_table} -A {iptables_chain} -j NETFLOW -{iface_option} {iface}'
127+
iptables_commands.append(rule_definition)
128+
129+
# change iptables
130+
for command in iptables_commands:
131+
cmd(command, raising=ConfigError)
132+
133+
def _iptables_config_v4_and_v6(configured_ifaces, direction):
134+
for command in 'iptables', 'ip6tables':
135+
_iptables_config(command, configured_ifaces, direction)
136+
137+
def set_watched_iptables_interfaces(ingress_interfaces, egress_interfaces):
138+
"""
139+
Update iptables and ip6tables rules so that ipt_NETFLOW watches
140+
exact list of interfaces in ingress_interfaces for ingress table/chain
141+
and egress_interfaces for egress table/chain
142+
"""
143+
_iptables_config_v4_and_v6(ingress_interfaces, 'ingress')
144+
_iptables_config_v4_and_v6(egress_interfaces, 'egress')
145+
146+
def stop():
147+
"""
148+
Stop ipt_NETFLOW: remove all iptables rules that use it
149+
and remove module
150+
"""
151+
set_watched_iptables_interfaces([], [])
152+
153+
unload_kmod(module_name)
154+
155+
def start(ingress_interfaces, egress_interfaces):
156+
"""
157+
Start ipt_NETFLOW:
158+
159+
* Load ipt_NETFLOW kernel module
160+
* Install iptables and ip6tables rules for
161+
ingress_interfaces and egress_interfaces
162+
"""
163+
164+
check_kmod(module_name)
165+
166+
set_watched_iptables_interfaces(ingress_interfaces, egress_interfaces)

src/conf_mode/system_flow-accounting.py

Lines changed: 14 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -27,135 +27,22 @@
2727
from vyos.template import is_ipv4
2828
from vyos.template import is_ipv6
2929
from vyos.template import render
30-
from vyos.utils.kernel import check_kmod
31-
from vyos.utils.kernel import unload_kmod
3230
from vyos.utils.process import call
33-
from vyos.utils.process import cmd
3431
from vyos.utils.process import run
3532
from vyos.utils.network import is_addr_assigned
3633
from vyos import ConfigError
3734
from vyos import airbag
35+
from vyos import ipt_netflow
3836
airbag.enable()
3937

4038
ipt_netflow_conf_path = '/etc/modprobe.d/ipt_NETFLOW.conf'
41-
module_name = 'ipt_NETFLOW'
42-
iptables_ingress_netflow_table = 'raw'
43-
iptables_ingress_netflow_chain = 'PREROUTING'
44-
iptables_egress_netflow_table = 'mangle'
45-
iptables_egress_netflow_chain = 'POSTROUTING'
39+
4640
# Variable to store between generate and apply
4741
# whether module configuration was changed
4842
# and module reload is needed
4943
need_reload = True
5044

5145

52-
# get iptables rule dict for chain in table
53-
def _iptables_get_rules(command, chain, table):
54-
# define list with rules
55-
rules = []
56-
57-
# run iptables, save output and split it by lines
58-
iptables_command = f'{command} -vn -t {table} -L {chain}'
59-
tmp = cmd(iptables_command, message='Failed to get flows list')
60-
lines = tmp.splitlines()
61-
62-
# Sample output to parse:
63-
#vyos@vyos:~$ sudo iptables -vn -t raw -L PREROUTING
64-
#Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
65-
# pkts bytes target prot opt in out source destination
66-
# 0 0 NETFLOW 0 -- eth0 * 0.0.0.0/0 0.0.0.0/0 NETFLOW
67-
68-
# Check that format is as expected
69-
if len(lines) < 2:
70-
raise ConfigError(f'Unexpected output from {command}, too few lines')
71-
if not lines[0].startswith(f'Chain {chain}'):
72-
raise ConfigError(f'Unexpected first line in output of {command}: "{lines[0]}"')
73-
columns = lines[1].split();
74-
75-
# parse each line and add information to list
76-
rulenum = 0
77-
for current_rule in lines[2:]:
78-
rulenum += 1
79-
current_rule_parsed = current_rule.split()
80-
current_rule_parsed = {
81-
columns[i]: current_rule_parsed[i]
82-
for i in range(min(len(current_rule_parsed), len(columns)))
83-
}
84-
if current_rule_parsed.get('target', '') != 'NETFLOW':
85-
continue
86-
87-
rules.append({
88-
'interface-in': current_rule_parsed.get("in", ''),
89-
'interface-out': current_rule_parsed.get("out", ''),
90-
'table': table,
91-
'rulenum': rulenum
92-
})
93-
94-
# return list with rules
95-
return rules
96-
97-
def _iptables_config(command, configured_ifaces, direction):
98-
# define list of nftables commands to modify settings
99-
iptables_commands = []
100-
101-
if direction == "ingress":
102-
iptables_table = iptables_ingress_netflow_table
103-
iptables_chain = iptables_ingress_netflow_chain
104-
elif direction == "egress":
105-
iptables_table = iptables_egress_netflow_table
106-
iptables_chain = iptables_egress_netflow_chain
107-
else:
108-
raise ConfigError(f'_iptables_config: Unexpected direction="{direction}"')
109-
110-
# prepare extended list with configured interfaces
111-
configured_ifaces_extended = []
112-
for iface in configured_ifaces:
113-
configured_ifaces_extended.append({ 'iface': iface })
114-
115-
# get currently configured interfaces with iptables rules
116-
active_rules = _iptables_get_rules(command, iptables_chain, iptables_table)
117-
118-
# compare current active list with configured one and delete excessive interfaces, add missed
119-
active_ifaces = []
120-
interface_key = 'interface-out' if direction == "egress" else "interface-in"
121-
rulenums_delete = []
122-
for rule in active_rules:
123-
interface = rule[interface_key]
124-
if interface not in configured_ifaces:
125-
rulenums_delete.append(rule['rulenum'])
126-
else:
127-
active_ifaces.append({
128-
'iface': interface,
129-
})
130-
131-
# It is important to delete rule with bigger rulenum first, so that other
132-
# rulenums are not changed
133-
rulenums_delete.sort(reverse=True)
134-
for rulenum in rulenums_delete:
135-
iptables_commands.append(f'{command} -t {iptables_table} -D {iptables_chain} {rulenum}')
136-
137-
# do not create new rules for already configured interfaces
138-
for iface in active_ifaces:
139-
if iface in configured_ifaces_extended:
140-
configured_ifaces_extended.remove(iface)
141-
142-
# create missed rules
143-
for iface_extended in configured_ifaces_extended:
144-
iface = iface_extended['iface']
145-
iface_option = "o" if direction == "egress" else "i"
146-
#iptables -t raw -A PREROUTING -j NETFLOW -i eth0
147-
rule_definition = f'{command} -t {iptables_table} -A {iptables_chain} -j NETFLOW -{iface_option} {iface}'
148-
iptables_commands.append(rule_definition)
149-
150-
# change iptables
151-
for command in iptables_commands:
152-
cmd(command, raising=ConfigError)
153-
154-
def _iptables_config_v4_and_v6(configured_ifaces, direction):
155-
for command in 'iptables', 'ip6tables':
156-
_iptables_config(command, configured_ifaces, direction)
157-
158-
15946
def get_config(config=None):
16047
if config:
16148
conf = config
@@ -272,30 +159,29 @@ def apply(flow_config):
272159
# all iptables usage of ipt_NETFLOW
273160
# When flow_config is disabled everything should be cleaned-up too
274161
if need_reload or not flow_config:
275-
_iptables_config_v4_and_v6([], 'ingress')
276-
_iptables_config_v4_and_v6([], 'egress')
277-
278-
# Stop flow-accounting module
279-
unload_kmod(module_name)
162+
ipt_netflow.stop()
280163

281164
if not flow_config:
282165
if os.path.exists(ipt_netflow_conf_path):
283166
os.unlink(ipt_netflow_conf_path)
284167
return
285-
286-
if need_reload:
287-
check_kmod(module_name)
168+
169+
ingress_interfaces = []
170+
egress_interfaces = []
288171

289172
# configure iptables for defined interfaces
290173
if 'interface' in flow_config['netflow']:
291-
interfaces = flow_config['netflow']['interface']
292-
_iptables_config_v4_and_v6(interfaces, 'ingress')
174+
ingress_interfaces = flow_config['netflow']['interface']
293175

294176
# configure egress the same way if configured otherwise remove it
295177
if 'enable_egress' in flow_config:
296-
_iptables_config_v4_and_v6(interfaces, 'egress')
297-
else:
298-
_iptables_config_v4_and_v6([], 'egress')
178+
egress_interfaces = ingress_interfaces
179+
180+
if need_reload:
181+
ipt_netflow.start(ingress_interfaces, egress_interfaces)
182+
else:
183+
ipt_netflow.set_watched_iptables_interfaces(ingress_interfaces, egress_interfaces)
184+
299185

300186

301187
if __name__ == '__main__':

0 commit comments

Comments
 (0)