|
27 | 27 | from vyos.template import is_ipv4 |
28 | 28 | from vyos.template import is_ipv6 |
29 | 29 | from vyos.template import render |
30 | | -from vyos.utils.kernel import check_kmod |
31 | | -from vyos.utils.kernel import unload_kmod |
32 | 30 | from vyos.utils.process import call |
33 | | -from vyos.utils.process import cmd |
34 | 31 | from vyos.utils.process import run |
35 | 32 | from vyos.utils.network import is_addr_assigned |
36 | 33 | from vyos import ConfigError |
37 | 34 | from vyos import airbag |
| 35 | +from vyos import ipt_netflow |
38 | 36 | airbag.enable() |
39 | 37 |
|
40 | 38 | 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 | + |
46 | 40 | # Variable to store between generate and apply |
47 | 41 | # whether module configuration was changed |
48 | 42 | # and module reload is needed |
49 | 43 | need_reload = True |
50 | 44 |
|
51 | 45 |
|
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 | | - |
159 | 46 | def get_config(config=None): |
160 | 47 | if config: |
161 | 48 | conf = config |
@@ -272,30 +159,29 @@ def apply(flow_config): |
272 | 159 | # all iptables usage of ipt_NETFLOW |
273 | 160 | # When flow_config is disabled everything should be cleaned-up too |
274 | 161 | 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() |
280 | 163 |
|
281 | 164 | if not flow_config: |
282 | 165 | if os.path.exists(ipt_netflow_conf_path): |
283 | 166 | os.unlink(ipt_netflow_conf_path) |
284 | 167 | return |
285 | | - |
286 | | - if need_reload: |
287 | | - check_kmod(module_name) |
| 168 | + |
| 169 | + ingress_interfaces = [] |
| 170 | + egress_interfaces = [] |
288 | 171 |
|
289 | 172 | # configure iptables for defined interfaces |
290 | 173 | 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'] |
293 | 175 |
|
294 | 176 | # configure egress the same way if configured otherwise remove it |
295 | 177 | 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 | + |
299 | 185 |
|
300 | 186 |
|
301 | 187 | if __name__ == '__main__': |
|
0 commit comments