Line 0
Link Here
|
|
|
1 |
From 2dd71331d4d204466e7b066f62952990e55c2e24 Mon Sep 17 00:00:00 2001 |
2 |
From: Alexander Nusov <alexander.nusov@nfvexpress.com> |
3 |
Date: Tue, 29 Nov 2016 14:21:41 +0300 |
4 |
Subject: [PATCH] add freebsd_net driver |
5 |
|
6 |
--- |
7 |
nova/network/freebsd_net.py | 1226 +++++++++++++++++++++++++++++++++++++++++++ |
8 |
1 file changed, 1226 insertions(+) |
9 |
create mode 100644 nova/network/freebsd_net.py |
10 |
|
11 |
diff --git a/nova/network/freebsd_net.py b/nova/network/freebsd_net.py |
12 |
new file mode 100644 |
13 |
index 0000000..b71fcf6 |
14 |
--- /dev/null |
15 |
+++ b/nova/network/freebsd_net.py |
16 |
@@ -0,0 +1,1226 @@ |
17 |
+# Copyright (c) 2011 X.commerce, a business unit of eBay Inc. |
18 |
+# Copyright 2010 United States Government as represented by the |
19 |
+# Administrator of the National Aeronautics and Space Administration. |
20 |
+# All Rights Reserved. |
21 |
+# |
22 |
+# Licensed under the Apache License, Version 2.0 (the "License"); you may |
23 |
+# not use this file except in compliance with the License. You may obtain |
24 |
+# a copy of the License at |
25 |
+# |
26 |
+# http://www.apache.org/licenses/LICENSE-2.0 |
27 |
+# |
28 |
+# Unless required by applicable law or agreed to in writing, software |
29 |
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
30 |
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
31 |
+# License for the specific language governing permissions and limitations |
32 |
+# under the License. |
33 |
+ |
34 |
+"""Implements vlans, bridges, and iptables rules using linux utilities.""" |
35 |
+ |
36 |
+import calendar |
37 |
+import inspect |
38 |
+import os |
39 |
+import re |
40 |
+import time |
41 |
+import json |
42 |
+ |
43 |
+import netaddr |
44 |
+import netifaces |
45 |
+import socket |
46 |
+import struct |
47 |
+ |
48 |
+from oslo_concurrency import processutils |
49 |
+from oslo_log import log as logging |
50 |
+from oslo_serialization import jsonutils |
51 |
+from oslo_utils import excutils |
52 |
+from oslo_utils import fileutils |
53 |
+from oslo_utils import importutils |
54 |
+from oslo_utils import timeutils |
55 |
+import six |
56 |
+ |
57 |
+import nova.conf |
58 |
+from nova import exception |
59 |
+from nova.i18n import _, _LE, _LW |
60 |
+from nova.network import model as network_model |
61 |
+from nova import objects |
62 |
+from nova.pci import utils as pci_utils |
63 |
+from nova import utils |
64 |
+ |
65 |
+LOG = logging.getLogger(__name__) |
66 |
+ |
67 |
+ |
68 |
+CONF = nova.conf.CONF |
69 |
+ |
70 |
+ |
71 |
+# NOTE(vish): Iptables supports chain names of up to 28 characters, and we |
72 |
+# add up to 12 characters to binary_name which is used as a prefix, |
73 |
+# so we limit it to 16 characters. |
74 |
+# (max_chain_name_length - len('-POSTROUTING') == 16) |
75 |
+def get_binary_name(): |
76 |
+ """Grab the name of the binary we're running in.""" |
77 |
+ return os.path.basename(inspect.stack()[-1][1])[:16] |
78 |
+ |
79 |
+binary_name = get_binary_name() |
80 |
+ |
81 |
+ |
82 |
+# NOTE(jkoelker) This is just a nice little stub point since mocking |
83 |
+# builtins with mox is a nightmare |
84 |
+def write_to_file(file, data, mode='w'): |
85 |
+ with open(file, mode) as f: |
86 |
+ f.write(data) |
87 |
+ |
88 |
+ |
89 |
+def is_pid_cmdline_correct(pid, match): |
90 |
+ """Ensure that the cmdline for a pid seems sane |
91 |
+ |
92 |
+ Because pids are recycled, blindly killing by pid is something to |
93 |
+ avoid. This provides the ability to include a substring that is |
94 |
+ expected in the cmdline as a safety check. |
95 |
+ """ |
96 |
+ try: |
97 |
+ with open('/proc/%d/cmdline' % pid) as f: |
98 |
+ cmdline = f.read() |
99 |
+ return match in cmdline |
100 |
+ except EnvironmentError: |
101 |
+ return False |
102 |
+ |
103 |
+ |
104 |
+def metadata_forward(): |
105 |
+ """Create forwarding rule for metadata.""" |
106 |
+ firewall_manager.add_rule("rdr proto tcp from any to 169.254.169.254 " |
107 |
+ "port 80 -> %s port %s" % |
108 |
+ (CONF.metadata_host, CONF.metadata_port)) |
109 |
+ firewall_manager.add_rule("pass out route-to (lo0 127.0.0.1) proto tcp " |
110 |
+ "from any to 169.254.169.254 port 80") |
111 |
+ firewall_manager.apply() |
112 |
+ |
113 |
+ |
114 |
+def metadata_accept(): |
115 |
+ """Create the filter accept rule for metadata.""" |
116 |
+ firewall_manager.add_rule("pass in inet proto tcp from any to " |
117 |
+ "169.254.169.254 port = http " |
118 |
+ "flags S/SA keep state") |
119 |
+ firewall_manager.apply() |
120 |
+ |
121 |
+ |
122 |
+def init_host(ip_range, is_external=False): |
123 |
+ """Basic networking setup goes here.""" |
124 |
+ # NOTE(devcamcar): Cloud public SNAT entries and the default |
125 |
+ # SNAT rule for outbound traffic. |
126 |
+ |
127 |
+ firewall_manager.add_snat_rule(ip_range, is_external) |
128 |
+ if is_external: |
129 |
+ for snat_range in CONF.force_snat_range: |
130 |
+ firewall_manager.add_rule("pass quick inet from %s to %s" % |
131 |
+ (ip_range, snat_range)) |
132 |
+ firewall_manager.add_rule("pass quick inet from %s to %s/32" % |
133 |
+ (ip_range, CONF.metadata_host)) |
134 |
+ for dmz in CONF.dmz_cidr: |
135 |
+ firewall_manager.add_rule("pass quick inet from %s to %s" % |
136 |
+ (ip_range, dmz)) |
137 |
+ |
138 |
+ """ |
139 |
+ iptables_manager.ipv4['nat'].add_rule('POSTROUTING', |
140 |
+ '-s %(range)s -d %(range)s ' |
141 |
+ '-m conntrack ! --ctstate DNAT ' |
142 |
+ '-j ACCEPT' % |
143 |
+ {'range': ip_range}) |
144 |
+ """ |
145 |
+ firewall_manager.apply() |
146 |
+ |
147 |
+ |
148 |
+def send_arp_for_ip(ip, device, count): |
149 |
+ out, err = _execute('arping', '-U', '-i', device, '-c', str(count), ip, |
150 |
+ run_as_root=True, check_exit_code=False) |
151 |
+ |
152 |
+ if err: |
153 |
+ LOG.debug('arping error for IP %s', ip) |
154 |
+ |
155 |
+ |
156 |
+def bind_floating_ip(floating_ip, device): |
157 |
+ """Bind IP to public interface.""" |
158 |
+ _execute('ifconfig', device, str(floating_ip) + '/32', 'add', |
159 |
+ run_as_root=True, check_exit_code=0) |
160 |
+ |
161 |
+ if CONF.send_arp_for_ha and CONF.send_arp_for_ha_count > 0: |
162 |
+ send_arp_for_ip(floating_ip, device, CONF.send_arp_for_ha_count) |
163 |
+ |
164 |
+ |
165 |
+def unbind_floating_ip(floating_ip, device): |
166 |
+ """Unbind a public IP from public interface.""" |
167 |
+ _execute('ifconfig', device, str(floating_ip) + '/32', 'delete', |
168 |
+ run_as_root=True, check_exit_code=0) |
169 |
+ |
170 |
+ |
171 |
+def ensure_metadata_ip(): |
172 |
+ """Sets up local metadata IP.""" |
173 |
+ _execute('ifconfig', 'lo0', 'alias', '169.254.169.254/32', |
174 |
+ run_as_root=True, check_exit_code=0) |
175 |
+ |
176 |
+ |
177 |
+def ensure_vpn_forward(public_ip, port, private_ip): |
178 |
+ """Sets up forwarding rules for vlan.""" |
179 |
+ firewall_manager.add_rule("pass in proto udp " |
180 |
+ "from any to %s port 1194 " % |
181 |
+ (private_ip)) |
182 |
+ firewall_manager.add_rule("rdr proto udp from any to %s port %s -> " |
183 |
+ "%s port 1194" % |
184 |
+ (public_ip, port, private_ip)) |
185 |
+ firewall_manager.apply() |
186 |
+ |
187 |
+ |
188 |
+def ensure_floating_forward(floating_ip, fixed_ip, device, network): |
189 |
+ """Ensure floating IP forwarding rule.""" |
190 |
+ firewall_manager.ensure_floating_rules(floating_ip, fixed_ip, device) |
191 |
+ if device != network['bridge']: |
192 |
+ firewall_manager.ensure_in_network_traffic_rules(fixed_ip, network) |
193 |
+ firewall_manager.apply() |
194 |
+ |
195 |
+ |
196 |
+def remove_floating_forward(floating_ip, fixed_ip, device, network): |
197 |
+ """Remove forwarding for floating IP.""" |
198 |
+ firewall_manager.remove_floating_rules(floating_ip, fixed_ip, device) |
199 |
+ if device != network['bridge']: |
200 |
+ firewall_manager.remove_in_network_traffic_rules(fixed_ip, network) |
201 |
+ firewall_manager.apply() |
202 |
+ |
203 |
+ |
204 |
+def clean_conntrack(fixed_ip): |
205 |
+ pass |
206 |
+ |
207 |
+ |
208 |
+def _enable_ipv4_forwarding(): |
209 |
+ sysctl_key = 'net.inet.ip.forwarding' |
210 |
+ stdout, stderr = _execute('sysctl', '-n', sysctl_key) |
211 |
+ if stdout.strip() is not '1': |
212 |
+ _execute('sysctl', '%s=1' % sysctl_key, run_as_root=True) |
213 |
+ |
214 |
+ |
215 |
+@utils.synchronized('lock_gateway', external=True) |
216 |
+def initialize_gateway_device(dev, network_ref): |
217 |
+ if not network_ref: |
218 |
+ return |
219 |
+ |
220 |
+ _enable_ipv4_forwarding() |
221 |
+ |
222 |
+ # NOTE(vish): The ip for dnsmasq has to be the first address on the |
223 |
+ # bridge for it to respond to requests properly |
224 |
+ try: |
225 |
+ prefix = network_ref.cidr.prefixlen |
226 |
+ except AttributeError: |
227 |
+ prefix = network_ref['cidr'].rpartition('/')[2] |
228 |
+ |
229 |
+ full_ip = '%s/%s' % (network_ref['dhcp_server'], prefix) |
230 |
+ new_ip_params = [['inet', full_ip, 'broadcast', network_ref['broadcast']]] |
231 |
+ old_ip_params = [] |
232 |
+ out, err = _execute('ifconfig', dev) |
233 |
+ for line in out.split('\n'): |
234 |
+ fields = line.split() |
235 |
+ if fields and fields[0] == 'inet': |
236 |
+ old_ip_params.append(fields) |
237 |
+ if _address_to_cidr(fields[1], fields[3]) != full_ip: |
238 |
+ new_ip_params.append(fields) |
239 |
+ if not old_ip_params or _address_to_cidr(old_ip_params[0][1], old_ip_params[0][3]) != full_ip: |
240 |
+ old_routes = [] |
241 |
+ result = _execute('netstat', '-nrW', '-f', 'inet') |
242 |
+ if result: |
243 |
+ out, err = result |
244 |
+ for line in out.split('\n'): |
245 |
+ fields = line.split() |
246 |
+ if len(fields) > 6 and (fields[6] == dev) and ('G' in fields[2]): |
247 |
+ old_routes.append(fields) |
248 |
+ _execute('route', '-q', 'delete', fields[0], fields[1], |
249 |
+ run_as_root=True) |
250 |
+ for ip_params in old_ip_params: |
251 |
+ _execute(*_ifconfig_tail_cmd(dev, ip_params, 'delete'), |
252 |
+ run_as_root=True) |
253 |
+ for ip_params in new_ip_params: |
254 |
+ _execute(*_ifconfig_tail_cmd(dev, ip_params, 'add'), |
255 |
+ run_as_root=True) |
256 |
+ |
257 |
+ for fields in old_routes: |
258 |
+ _execute('route', '-q', 'add', fields[0], fields[1], |
259 |
+ run_as_root=True) |
260 |
+ if CONF.send_arp_for_ha and CONF.send_arp_for_ha_count > 0: |
261 |
+ send_arp_for_ip(network_ref['dhcp_server'], dev, |
262 |
+ CONF.send_arp_for_ha_count) |
263 |
+ if CONF.use_ipv6: |
264 |
+ _execute('ifconfig', dev, 'inet6', network_ref['cidr_v6'], |
265 |
+ run_as_root=True) |
266 |
+ |
267 |
+ |
268 |
+def get_dhcp_leases(context, network_ref): |
269 |
+ """Return a network's hosts config in dnsmasq leasefile format.""" |
270 |
+ hosts = [] |
271 |
+ host = None |
272 |
+ if network_ref['multi_host']: |
273 |
+ host = CONF.host |
274 |
+ for fixedip in objects.FixedIPList.get_by_network(context, |
275 |
+ network_ref, |
276 |
+ host=host): |
277 |
+ # NOTE(cfb): Don't return a lease entry if the IP isn't |
278 |
+ # already leased |
279 |
+ if fixedip.leased: |
280 |
+ hosts.append(_host_lease(fixedip)) |
281 |
+ |
282 |
+ return '\n'.join(hosts) |
283 |
+ |
284 |
+ |
285 |
+def get_dhcp_hosts(context, network_ref, fixedips): |
286 |
+ """Get network's hosts config in dhcp-host format.""" |
287 |
+ hosts = [] |
288 |
+ macs = set() |
289 |
+ for fixedip in fixedips: |
290 |
+ if fixedip.allocated: |
291 |
+ if fixedip.virtual_interface.address not in macs: |
292 |
+ hosts.append(_host_dhcp(fixedip)) |
293 |
+ macs.add(fixedip.virtual_interface.address) |
294 |
+ return '\n'.join(hosts) |
295 |
+ |
296 |
+ |
297 |
+def get_dns_hosts(context, network_ref): |
298 |
+ """Get network's DNS hosts in hosts format.""" |
299 |
+ hosts = [] |
300 |
+ for fixedip in objects.FixedIPList.get_by_network(context, network_ref): |
301 |
+ if fixedip.allocated: |
302 |
+ hosts.append(_host_dns(fixedip)) |
303 |
+ return '\n'.join(hosts) |
304 |
+ |
305 |
+ |
306 |
+def _add_dnsmasq_accept_rules(dev): |
307 |
+ """Allow DHCP and DNS traffic through to dnsmasq.""" |
308 |
+ for port in [67, 53]: |
309 |
+ for proto in ['udp', 'tcp']: |
310 |
+ firewall_manager.add_rule("pass in on %s inet proto %s " |
311 |
+ "from any to any port %s" % |
312 |
+ (dev, proto, port)) |
313 |
+ firewall_manager.apply() |
314 |
+ |
315 |
+ |
316 |
+def _remove_dnsmasq_accept_rules(dev): |
317 |
+ """Remove DHCP and DNS traffic allowed through to dnsmasq.""" |
318 |
+ for port in [67, 53]: |
319 |
+ for proto in ['udp', 'tcp']: |
320 |
+ firewall_manager.remove_rule("pass in on %s inet proto %s " |
321 |
+ "from any to any port %s" % |
322 |
+ (dev, proto, port)) |
323 |
+ firewall_manager.apply() |
324 |
+ |
325 |
+ |
326 |
+def get_dhcp_opts(context, network_ref, fixedips): |
327 |
+ """Get network's hosts config in dhcp-opts format.""" |
328 |
+ gateway = network_ref['gateway'] |
329 |
+ # NOTE(vish): if we are in multi-host mode and we are not sharing |
330 |
+ # addresses, then we actually need to hand out the |
331 |
+ # dhcp server address as the gateway. |
332 |
+ if network_ref['multi_host'] and not (network_ref['share_address'] or |
333 |
+ CONF.share_dhcp_address): |
334 |
+ gateway = network_ref['dhcp_server'] |
335 |
+ hosts = [] |
336 |
+ if CONF.use_single_default_gateway: |
337 |
+ for fixedip in fixedips: |
338 |
+ if fixedip.allocated: |
339 |
+ vif_id = fixedip.virtual_interface_id |
340 |
+ if fixedip.default_route: |
341 |
+ hosts.append(_host_dhcp_opts(vif_id, gateway)) |
342 |
+ else: |
343 |
+ hosts.append(_host_dhcp_opts(vif_id)) |
344 |
+ else: |
345 |
+ hosts.append(_host_dhcp_opts(None, gateway)) |
346 |
+ return '\n'.join(hosts) |
347 |
+ |
348 |
+ |
349 |
+def release_dhcp(dev, address, mac_address): |
350 |
+ if device_exists(dev): |
351 |
+ try: |
352 |
+ utils.execute('dhcp_release', dev, address, mac_address, |
353 |
+ run_as_root=True) |
354 |
+ except processutils.ProcessExecutionError: |
355 |
+ raise exception.NetworkDhcpReleaseFailed(address=address, |
356 |
+ mac_address=mac_address) |
357 |
+ |
358 |
+ |
359 |
+def update_dhcp(context, dev, network_ref): |
360 |
+ conffile = _dhcp_file(dev, 'conf') |
361 |
+ host = None |
362 |
+ if network_ref['multi_host']: |
363 |
+ host = CONF.host |
364 |
+ fixedips = objects.FixedIPList.get_by_network(context, |
365 |
+ network_ref, |
366 |
+ host=host) |
367 |
+ write_to_file(conffile, get_dhcp_hosts(context, network_ref, fixedips)) |
368 |
+ restart_dhcp(context, dev, network_ref, fixedips) |
369 |
+ |
370 |
+ |
371 |
+def update_dns(context, dev, network_ref): |
372 |
+ hostsfile = _dhcp_file(dev, 'hosts') |
373 |
+ host = None |
374 |
+ if network_ref['multi_host']: |
375 |
+ host = CONF.host |
376 |
+ fixedips = objects.FixedIPList.get_by_network(context, |
377 |
+ network_ref, |
378 |
+ host=host) |
379 |
+ write_to_file(hostsfile, get_dns_hosts(context, network_ref)) |
380 |
+ restart_dhcp(context, dev, network_ref, fixedips) |
381 |
+ |
382 |
+ |
383 |
+def kill_dhcp(dev): |
384 |
+ pid = _dnsmasq_pid_for(dev) |
385 |
+ if pid: |
386 |
+ # Check that the process exists and looks like a dnsmasq process |
387 |
+ conffile = _dhcp_file(dev, 'conf') |
388 |
+ if is_pid_cmdline_correct(pid, conffile.split('/')[-1]): |
389 |
+ _execute('kill', '-9', pid, run_as_root=True) |
390 |
+ else: |
391 |
+ LOG.debug('Pid %d is stale, skip killing dnsmasq', pid) |
392 |
+ _remove_dnsmasq_accept_rules(dev) |
393 |
+ |
394 |
+ |
395 |
+# NOTE(ja): Sending a HUP only reloads the hostfile, so any |
396 |
+# configuration options (like dchp-range, vlan, ...) |
397 |
+# aren't reloaded. |
398 |
+@utils.synchronized('dnsmasq_start') |
399 |
+def restart_dhcp(context, dev, network_ref, fixedips): |
400 |
+ """(Re)starts a dnsmasq server for a given network. |
401 |
+ |
402 |
+ If a dnsmasq instance is already running then send a HUP |
403 |
+ signal causing it to reload, otherwise spawn a new instance. |
404 |
+ |
405 |
+ """ |
406 |
+ conffile = _dhcp_file(dev, 'conf') |
407 |
+ |
408 |
+ optsfile = _dhcp_file(dev, 'opts') |
409 |
+ write_to_file(optsfile, get_dhcp_opts(context, network_ref, fixedips)) |
410 |
+ os.chmod(optsfile, 0o644) |
411 |
+ |
412 |
+ # Make sure dnsmasq can actually read it (it setuid()s to "nobody") |
413 |
+ os.chmod(conffile, 0o644) |
414 |
+ |
415 |
+ pid = _dnsmasq_pid_for(dev) |
416 |
+ |
417 |
+ # if dnsmasq is already running, then tell it to reload |
418 |
+ if pid: |
419 |
+ if is_pid_cmdline_correct(pid, conffile.split('/')[-1]): |
420 |
+ try: |
421 |
+ _execute('kill', '-HUP', pid, run_as_root=True) |
422 |
+ _add_dnsmasq_accept_rules(dev) |
423 |
+ return |
424 |
+ except Exception as exc: |
425 |
+ LOG.error(_LE('kill -HUP dnsmasq threw %s'), exc) |
426 |
+ else: |
427 |
+ LOG.debug('Pid %d is stale, relaunching dnsmasq', pid) |
428 |
+ |
429 |
+ cmd = ['env', |
430 |
+ 'CONFIG_FILE=%s' % jsonutils.dumps(CONF.dhcpbridge_flagfile), |
431 |
+ 'NETWORK_ID=%s' % str(network_ref['id']), |
432 |
+ 'dnsmasq', |
433 |
+ '--strict-order', |
434 |
+ '--bind-interfaces', |
435 |
+ '--conf-file=%s' % CONF.dnsmasq_config_file, |
436 |
+ '--pid-file=%s' % _dhcp_file(dev, 'pid'), |
437 |
+ '--dhcp-optsfile=%s' % _dhcp_file(dev, 'opts'), |
438 |
+ '--listen-address=%s' % network_ref['dhcp_server'], |
439 |
+ '--except-interface=lo', |
440 |
+ '--dhcp-range=set:%s,%s,static,%s,%ss' % |
441 |
+ (network_ref['label'], |
442 |
+ network_ref['dhcp_start'], |
443 |
+ network_ref['netmask'], |
444 |
+ CONF.dhcp_lease_time), |
445 |
+ '--dhcp-lease-max=%s' % len(netaddr.IPNetwork(network_ref['cidr'])), |
446 |
+ '--dhcp-hostsfile=%s' % _dhcp_file(dev, 'conf'), |
447 |
+ '--dhcp-script=%s' % CONF.dhcpbridge, |
448 |
+ '--no-hosts', |
449 |
+ '--leasefile-ro'] |
450 |
+ |
451 |
+ # dnsmasq currently gives an error for an empty domain, |
452 |
+ # rather than ignoring. So only specify it if defined. |
453 |
+ if CONF.dhcp_domain: |
454 |
+ cmd.append('--domain=%s' % CONF.dhcp_domain) |
455 |
+ |
456 |
+ dns_servers = CONF.dns_server |
457 |
+ if CONF.use_network_dns_servers: |
458 |
+ if network_ref.get('dns1'): |
459 |
+ dns_servers.append(network_ref.get('dns1')) |
460 |
+ if network_ref.get('dns2'): |
461 |
+ dns_servers.append(network_ref.get('dns2')) |
462 |
+ if network_ref['multi_host']: |
463 |
+ cmd.append('--addn-hosts=%s' % _dhcp_file(dev, 'hosts')) |
464 |
+ if dns_servers: |
465 |
+ cmd.append('--no-resolv') |
466 |
+ for dns_server in dns_servers: |
467 |
+ cmd.append('--server=%s' % dns_server) |
468 |
+ |
469 |
+ _execute(*cmd, run_as_root=True) |
470 |
+ |
471 |
+ _add_dnsmasq_accept_rules(dev) |
472 |
+ |
473 |
+ |
474 |
+@utils.synchronized('radvd_start') |
475 |
+def update_ra(context, dev, network_ref): |
476 |
+ conffile = _ra_file(dev, 'conf') |
477 |
+ conf_str = """ |
478 |
+interface %s |
479 |
+{ |
480 |
+ AdvSendAdvert on; |
481 |
+ MinRtrAdvInterval 3; |
482 |
+ MaxRtrAdvInterval 10; |
483 |
+ prefix %s |
484 |
+ { |
485 |
+ AdvOnLink on; |
486 |
+ AdvAutonomous on; |
487 |
+ }; |
488 |
+}; |
489 |
+""" % (dev, network_ref['cidr_v6']) |
490 |
+ write_to_file(conffile, conf_str) |
491 |
+ |
492 |
+ # Make sure radvd can actually read it (it setuid()s to "nobody") |
493 |
+ os.chmod(conffile, 0o644) |
494 |
+ |
495 |
+ pid = _ra_pid_for(dev) |
496 |
+ |
497 |
+ # if radvd is already running, then tell it to reload |
498 |
+ if pid: |
499 |
+ if is_pid_cmdline_correct(pid, conffile): |
500 |
+ try: |
501 |
+ _execute('kill', pid, run_as_root=True) |
502 |
+ except Exception as exc: |
503 |
+ LOG.error(_LE('killing radvd threw %s'), exc) |
504 |
+ else: |
505 |
+ LOG.debug('Pid %d is stale, relaunching radvd', pid) |
506 |
+ |
507 |
+ cmd = ['radvd', |
508 |
+ '-C', '%s' % _ra_file(dev, 'conf'), |
509 |
+ '-p', '%s' % _ra_file(dev, 'pid')] |
510 |
+ |
511 |
+ _execute(*cmd, run_as_root=True) |
512 |
+ |
513 |
+ |
514 |
+def _host_lease(fixedip): |
515 |
+ """Return a host string for an address in leasefile format.""" |
516 |
+ timestamp = timeutils.utcnow() |
517 |
+ seconds_since_epoch = calendar.timegm(timestamp.utctimetuple()) |
518 |
+ return '%d %s %s %s *' % (seconds_since_epoch + CONF.dhcp_lease_time, |
519 |
+ fixedip.virtual_interface.address, |
520 |
+ fixedip.address, |
521 |
+ fixedip.instance.hostname or '*') |
522 |
+ |
523 |
+ |
524 |
+def _host_dhcp_network(vif_id): |
525 |
+ return 'NW-%s' % vif_id |
526 |
+ |
527 |
+ |
528 |
+def _host_dhcp(fixedip): |
529 |
+ """Return a host string for an address in dhcp-host format.""" |
530 |
+ # NOTE(cfb): dnsmasq on linux only supports 64 characters in the hostname |
531 |
+ # field (LP #1238910). Since the . counts as a character we need |
532 |
+ # to truncate the hostname to only 63 characters. |
533 |
+ hostname = fixedip.instance.hostname |
534 |
+ if len(hostname) > 63: |
535 |
+ LOG.warning(_LW('hostname %s too long, truncating.'), hostname) |
536 |
+ hostname = fixedip.instance.hostname[:2] + '-' +\ |
537 |
+ fixedip.instance.hostname[-60:] |
538 |
+ if CONF.use_single_default_gateway: |
539 |
+ net = _host_dhcp_network(fixedip.virtual_interface_id) |
540 |
+ return '%s,%s.%s,%s,net:%s' % (fixedip.virtual_interface.address, |
541 |
+ hostname, |
542 |
+ CONF.dhcp_domain, |
543 |
+ fixedip.address, |
544 |
+ net) |
545 |
+ else: |
546 |
+ return '%s,%s.%s,%s' % (fixedip.virtual_interface.address, |
547 |
+ hostname, |
548 |
+ CONF.dhcp_domain, |
549 |
+ fixedip.address) |
550 |
+ |
551 |
+ |
552 |
+def _host_dns(fixedip): |
553 |
+ return '%s\t%s.%s' % (fixedip.address, |
554 |
+ fixedip.instance.hostname, |
555 |
+ CONF.dhcp_domain) |
556 |
+ |
557 |
+ |
558 |
+def _host_dhcp_opts(vif_id=None, gateway=None): |
559 |
+ """Return an empty gateway option.""" |
560 |
+ values = [] |
561 |
+ if vif_id is not None: |
562 |
+ values.append(_host_dhcp_network(vif_id)) |
563 |
+ # NOTE(vish): 3 is the dhcp option for gateway. |
564 |
+ values.append('3') |
565 |
+ if gateway: |
566 |
+ values.append('%s' % gateway) |
567 |
+ return ','.join(values) |
568 |
+ |
569 |
+ |
570 |
+def _execute(*cmd, **kwargs): |
571 |
+ """Wrapper around utils._execute for fake_network.""" |
572 |
+ if CONF.fake_network: |
573 |
+ LOG.debug('FAKE NET: %s', ' '.join(map(str, cmd))) |
574 |
+ return 'fake', 0 |
575 |
+ else: |
576 |
+ return utils.execute(*cmd, **kwargs) |
577 |
+ |
578 |
+ |
579 |
+def device_exists(device): |
580 |
+ """Check if ethernet device exists.""" |
581 |
+ try: |
582 |
+ _execute('ifconfig', device, run_as_root=True, check_exit_code=[0]) |
583 |
+ except processutils.ProcessExecutionError: |
584 |
+ return False |
585 |
+ else: |
586 |
+ return True |
587 |
+ |
588 |
+ |
589 |
+def _dhcp_file(dev, kind): |
590 |
+ """Return path to a pid, leases, hosts or conf file for a bridge/device.""" |
591 |
+ fileutils.ensure_tree(CONF.networks_path) |
592 |
+ return os.path.abspath('%s/nova-%s.%s' % (CONF.networks_path, |
593 |
+ dev, |
594 |
+ kind)) |
595 |
+ |
596 |
+ |
597 |
+def _ra_file(dev, kind): |
598 |
+ """Return path to a pid or conf file for a bridge/device.""" |
599 |
+ fileutils.ensure_tree(CONF.networks_path) |
600 |
+ return os.path.abspath('%s/nova-ra-%s.%s' % (CONF.networks_path, |
601 |
+ dev, |
602 |
+ kind)) |
603 |
+ |
604 |
+ |
605 |
+def _dnsmasq_pid_for(dev): |
606 |
+ """Returns the pid for prior dnsmasq instance for a bridge/device. |
607 |
+ |
608 |
+ Returns None if no pid file exists. |
609 |
+ |
610 |
+ If machine has rebooted pid might be incorrect (caller should check). |
611 |
+ |
612 |
+ """ |
613 |
+ pid_file = _dhcp_file(dev, 'pid') |
614 |
+ |
615 |
+ if os.path.exists(pid_file): |
616 |
+ try: |
617 |
+ with open(pid_file, 'r') as f: |
618 |
+ return int(f.read()) |
619 |
+ except (ValueError, IOError): |
620 |
+ return None |
621 |
+ |
622 |
+ |
623 |
+def _ra_pid_for(dev): |
624 |
+ """Returns the pid for prior radvd instance for a bridge/device. |
625 |
+ |
626 |
+ Returns None if no pid file exists. |
627 |
+ |
628 |
+ If machine has rebooted pid might be incorrect (caller should check). |
629 |
+ |
630 |
+ """ |
631 |
+ pid_file = _ra_file(dev, 'pid') |
632 |
+ |
633 |
+ if os.path.exists(pid_file): |
634 |
+ with open(pid_file, 'r') as f: |
635 |
+ return int(f.read()) |
636 |
+ |
637 |
+ |
638 |
+def _address_to_cidr(address, hexmask): |
639 |
+ """Produce a CIDR format address/netmask.""" |
640 |
+ netmask = socket.inet_ntoa(struct.pack(">I", int(hexmask, 16))) |
641 |
+ ip_cidr = netaddr.IPNetwork("%s/%s" % (address, netmask)) |
642 |
+ return str(ip_cidr) |
643 |
+ |
644 |
+ |
645 |
+def _ifconfig_tail_cmd(netif, params, action): |
646 |
+ """Construct ifconfig command""" |
647 |
+ cmd = ['ifconfig', netif] |
648 |
+ cmd.extend(params) |
649 |
+ cmd.extend([action]) |
650 |
+ return cmd |
651 |
+ |
652 |
+ |
653 |
+def _set_device_mtu(dev, mtu=None): |
654 |
+ """Set the device MTU.""" |
655 |
+ if mtu: |
656 |
+ utils.execute('ifconfig', dev, 'mtu', mtu, |
657 |
+ run_as_root=True, check_exit_code=0) |
658 |
+ |
659 |
+ |
660 |
+def _ovs_vsctl(args): |
661 |
+ full_args = ['ovs-vsctl', '--timeout=%s' % CONF.ovs_vsctl_timeout] + args |
662 |
+ try: |
663 |
+ return utils.execute(*full_args, run_as_root=True) |
664 |
+ except Exception as e: |
665 |
+ LOG.error(_LE("Unable to execute %(cmd)s. Exception: %(exception)s"), |
666 |
+ {'cmd': full_args, 'exception': e}) |
667 |
+ raise exception.OvsConfigurationFailure(inner_exception=e) |
668 |
+ |
669 |
+ |
670 |
+def _create_ovs_vif_cmd(bridge, dev, iface_id, mac, |
671 |
+ instance_id, interface_type=None): |
672 |
+ cmd = ['--', '--if-exists', 'del-port', dev, '--', |
673 |
+ 'add-port', bridge, dev, |
674 |
+ '--', 'set', 'Interface', dev, |
675 |
+ 'external-ids:iface-id=%s' % iface_id, |
676 |
+ 'external-ids:iface-status=active', |
677 |
+ 'external-ids:attached-mac=%s' % mac, |
678 |
+ 'external-ids:vm-uuid=%s' % instance_id] |
679 |
+ if interface_type: |
680 |
+ cmd += ['type=%s' % interface_type] |
681 |
+ return cmd |
682 |
+ |
683 |
+ |
684 |
+def create_ovs_vif_port(bridge, dev, iface_id, mac, instance_id, |
685 |
+ mtu=None, interface_type=None): |
686 |
+ _ovs_vsctl(_create_ovs_vif_cmd(bridge, dev, iface_id, |
687 |
+ mac, instance_id, |
688 |
+ interface_type)) |
689 |
+ # Note at present there is no support for setting the |
690 |
+ # mtu for vhost-user type ports. |
691 |
+ if interface_type != network_model.OVS_VHOSTUSER_INTERFACE_TYPE: |
692 |
+ _set_device_mtu(dev, mtu) |
693 |
+ else: |
694 |
+ LOG.debug("MTU not set on %(interface_name)s interface " |
695 |
+ "of type %(interface_type)s.", |
696 |
+ {'interface_name': dev, |
697 |
+ 'interface_type': interface_type}) |
698 |
+ |
699 |
+ |
700 |
+def delete_ovs_vif_port(bridge, dev, delete_dev=True): |
701 |
+ _ovs_vsctl(['--', '--if-exists', 'del-port', bridge, dev]) |
702 |
+ if delete_dev: |
703 |
+ delete_net_dev(dev) |
704 |
+ |
705 |
+ |
706 |
+def create_tap_dev(dev, mac_address=None): |
707 |
+ if not device_exists(dev): |
708 |
+ utils.execute('ifconfig', 'tap', 'create', 'name', dev, |
709 |
+ run_as_root=True, check_exit_code=[0]) |
710 |
+ if mac_address: |
711 |
+ utils.execute('ifconfig', dev, 'ether', mac_address, |
712 |
+ run_as_root=True, check_exit_code=[0]) |
713 |
+ utils.execute('ifconfig', dev, 'up', |
714 |
+ run_as_root=True, check_exit_code=[0]) |
715 |
+ |
716 |
+ |
717 |
+def delete_net_dev(dev): |
718 |
+ """Delete a network device only if it exists.""" |
719 |
+ if device_exists(dev): |
720 |
+ try: |
721 |
+ utils.execute('ifconfig', dev, 'destroy', |
722 |
+ run_as_root=True, check_exit_code=0) |
723 |
+ LOG.debug("Net device removed: '%s'", dev) |
724 |
+ except processutils.ProcessExecutionError: |
725 |
+ with excutils.save_and_reraise_exception(): |
726 |
+ LOG.error(_LE("Failed removing net device: '%s'"), dev) |
727 |
+ |
728 |
+ |
729 |
+def delete_bridge_dev(dev): |
730 |
+ """Delete a network bridge.""" |
731 |
+ if device_exists(dev): |
732 |
+ try: |
733 |
+ utils.execute('ifconfig', dev, 'down', run_as_root=True) |
734 |
+ utils.execute('ifconfig', dev, 'destroy', run_as_root=True) |
735 |
+ except processutils.ProcessExecutionError: |
736 |
+ with excutils.save_and_reraise_exception(): |
737 |
+ LOG.error(_LE("Failed removing bridge device: '%s'"), dev) |
738 |
+ |
739 |
+ |
740 |
+# Similar to compute virt layers, the FreeBSD network node |
741 |
+# code uses a flexible driver model to support different ways |
742 |
+# of creating ethernet interfaces and attaching them to the network. |
743 |
+# In the case of a network host, these interfaces |
744 |
+# act as gateway/dhcp/vpn/etc. endpoints not VM interfaces. |
745 |
+interface_driver = None |
746 |
+ |
747 |
+ |
748 |
+def _get_interface_driver(): |
749 |
+ global interface_driver |
750 |
+ if not interface_driver: |
751 |
+ interface_driver = importutils.import_object( |
752 |
+ CONF.freebsdnet_interface_driver) |
753 |
+ return interface_driver |
754 |
+ |
755 |
+ |
756 |
+def plug(network, mac_address, gateway=True): |
757 |
+ return _get_interface_driver().plug(network, mac_address, gateway) |
758 |
+ |
759 |
+ |
760 |
+def unplug(network): |
761 |
+ return _get_interface_driver().unplug(network) |
762 |
+ |
763 |
+ |
764 |
+def get_dev(network): |
765 |
+ return _get_interface_driver().get_dev(network) |
766 |
+ |
767 |
+ |
768 |
+class FreeBSDNetInterfaceDriver(object): |
769 |
+ """Abstract class that defines generic network host API |
770 |
+ for all FreeBSD interface drivers. |
771 |
+ """ |
772 |
+ |
773 |
+ def plug(self, network, mac_address): |
774 |
+ """Create FreeBSD device, return device name.""" |
775 |
+ raise NotImplementedError() |
776 |
+ |
777 |
+ def unplug(self, network): |
778 |
+ """Destroy FreeBSD device, return device name.""" |
779 |
+ raise NotImplementedError() |
780 |
+ |
781 |
+ def get_dev(self, network): |
782 |
+ """Get device name.""" |
783 |
+ raise NotImplementedError() |
784 |
+ |
785 |
+ |
786 |
+# plugs interfaces using FreeBSD Bridge |
787 |
+class FreeBSDBridgeInterfaceDriver(FreeBSDNetInterfaceDriver): |
788 |
+ |
789 |
+ def plug(self, network, mac_address, gateway=True): |
790 |
+ vlan = network.get('vlan') |
791 |
+ if vlan is not None: |
792 |
+ iface = CONF.vlan_interface or network['bridge_interface'] |
793 |
+ FreeBSDBridgeInterfaceDriver.ensure_vlan_bridge( |
794 |
+ vlan, |
795 |
+ network['bridge'], |
796 |
+ iface, |
797 |
+ network, |
798 |
+ mac_address, |
799 |
+ network.get('mtu')) |
800 |
+ iface = 'vlan%s' % vlan |
801 |
+ else: |
802 |
+ iface = CONF.flat_interface or network['bridge_interface'] |
803 |
+ FreeBSDBridgeInterfaceDriver.ensure_bridge( |
804 |
+ network['bridge'], |
805 |
+ iface, |
806 |
+ network, gateway) |
807 |
+ |
808 |
+ if network['share_address'] or CONF.share_dhcp_address: |
809 |
+ isolate_dhcp_address(iface, network['dhcp_server']) |
810 |
+ # NOTE(vish): applying here so we don't get a lock conflict |
811 |
+ firewall_manager.apply() |
812 |
+ return network['bridge'] |
813 |
+ |
814 |
+ def unplug(self, network, gateway=True): |
815 |
+ vlan = network.get('vlan') |
816 |
+ if vlan is not None: |
817 |
+ iface = 'vlan%s' % vlan |
818 |
+ FreeBSDBridgeInterfaceDriver.remove_vlan_bridge(vlan, |
819 |
+ network['bridge']) |
820 |
+ else: |
821 |
+ iface = CONF.flat_interface or network['bridge_interface'] |
822 |
+ FreeBSDBridgeInterfaceDriver.remove_bridge(network['bridge'], |
823 |
+ gateway) |
824 |
+ |
825 |
+ if network['share_address'] or CONF.share_dhcp_address: |
826 |
+ remove_isolate_dhcp_address(iface, network['dhcp_server']) |
827 |
+ |
828 |
+ firewall_manager.apply() |
829 |
+ return self.get_dev(network) |
830 |
+ |
831 |
+ def get_dev(self, network): |
832 |
+ return network['bridge'] |
833 |
+ |
834 |
+ @staticmethod |
835 |
+ def ensure_vlan_bridge(vlan_num, bridge, bridge_interface, |
836 |
+ net_attrs=None, mac_address=None, |
837 |
+ mtu=None): |
838 |
+ """Create a vlan and bridge unless they already exist.""" |
839 |
+ interface = FreeBSDBridgeInterfaceDriver.ensure_vlan(vlan_num, |
840 |
+ bridge_interface, mac_address, |
841 |
+ mtu) |
842 |
+ FreeBSDBridgeInterfaceDriver.ensure_bridge(bridge, interface, net_attrs) |
843 |
+ return interface |
844 |
+ |
845 |
+ @staticmethod |
846 |
+ def remove_vlan_bridge(vlan_num, bridge): |
847 |
+ """Delete a bridge and vlan.""" |
848 |
+ FreeBSDBridgeInterfaceDriver.remove_bridge(bridge) |
849 |
+ FreeBSDBridgeInterfaceDriver.remove_vlan(vlan_num) |
850 |
+ |
851 |
+ @staticmethod |
852 |
+ @utils.synchronized('lock_vlan', external=True) |
853 |
+ def ensure_vlan(vlan_num, bridge_interface, mac_address=None, mtu=None, |
854 |
+ interface=None): |
855 |
+ """Create a vlan unless it already exists.""" |
856 |
+ if interface is None: |
857 |
+ interface = 'vlan%s' % vlan_num |
858 |
+ if not device_exists(interface): |
859 |
+ LOG.debug('Starting VLAN interface %s', interface) |
860 |
+ out, err = _execute('ifconfig', 'vlan', 'create', |
861 |
+ 'vlan', vlan_num, |
862 |
+ 'vlandev', bridge_interface, |
863 |
+ 'name', interface, |
864 |
+ run_as_root=True) |
865 |
+ if err and 'File exists' not in err: |
866 |
+ msg = _('Failed to add vlan: %s') % err |
867 |
+ raise exception.NovaException(msg) |
868 |
+ # (danwent) the bridge will inherit this address, so we want to |
869 |
+ # make sure it is the value set from the NetworkManager |
870 |
+ if mac_address: |
871 |
+ _execute('ifconfig', interface, 'ether', mac_address, |
872 |
+ run_as_root=True) |
873 |
+ _execute('ifconfig',interface, 'up', |
874 |
+ run_as_root=True) |
875 |
+ # NOTE(vish): set mtu every time to ensure that changes to mtu get |
876 |
+ # propagated |
877 |
+ _set_device_mtu(interface, mtu) |
878 |
+ return interface |
879 |
+ |
880 |
+ @staticmethod |
881 |
+ @utils.synchronized('lock_vlan', external=True) |
882 |
+ def remove_vlan(vlan_num): |
883 |
+ """Delete a vlan.""" |
884 |
+ vlan_interface = 'vlan%s' % vlan_num |
885 |
+ delete_net_dev(vlan_interface) |
886 |
+ |
887 |
+ @staticmethod |
888 |
+ @utils.synchronized('lock_bridge', external=True) |
889 |
+ def ensure_bridge(bridge, interface, net_attrs=None, gateway=True, |
890 |
+ filtering=True): |
891 |
+ """Create a bridge unless it already exists. |
892 |
+ |
893 |
+ :param interface: the interface to create the bridge on. |
894 |
+ :param net_attrs: dictionary with attributes used to create bridge. |
895 |
+ :param gateway: whether or not the bridge is a gateway. |
896 |
+ :param filtering: whether or not to create filters on the bridge. |
897 |
+ |
898 |
+ If net_attrs is set, it will add the net_attrs['gateway'] to the bridge |
899 |
+ using net_attrs['broadcast'] and net_attrs['cidr']. It will also add |
900 |
+ the ip_v6 address specified in net_attrs['cidr_v6'] if use_ipv6 is set. |
901 |
+ |
902 |
+ The code will attempt to move any IPs that already exist on the |
903 |
+ interface onto the bridge and reset the default gateway if necessary. |
904 |
+ |
905 |
+ """ |
906 |
+ if not device_exists(bridge): |
907 |
+ LOG.debug('Starting Bridge %s', bridge) |
908 |
+ out, err = _execute('ifconfig', 'bridge', 'create', 'name', bridge, |
909 |
+ check_exit_code=False, run_as_root=True) |
910 |
+ if err and 'File exists' not in err: |
911 |
+ msg = _('Failed to add bridge: %s') % err |
912 |
+ raise exception.NovaException(msg) |
913 |
+ |
914 |
+ _execute('ifconfig', bridge, 'up', run_as_root=True) |
915 |
+ |
916 |
+ if interface: |
917 |
+ LOG.debug('Adding interface %(interface)s to bridge %(bridge)s', |
918 |
+ {'interface': interface, 'bridge': bridge}) |
919 |
+ out, err = _execute('ifconfig', bridge, 'addm', interface, |
920 |
+ check_exit_code=False, run_as_root=True) |
921 |
+ if err and 'File exists' not in err: |
922 |
+ msg = _('Failed to add interface: %s') % err |
923 |
+ raise exception.NovaException(msg) |
924 |
+ |
925 |
+ # NOTE(apmelton): Linux bridge's default behavior is to use the |
926 |
+ # lowest mac of all plugged interfaces. This isn't a problem when |
927 |
+ # it is first created and the only interface is the bridged |
928 |
+ # interface. But, as instance interfaces are plugged, there is a |
929 |
+ # chance for the mac to change. So, set it here so that it won't |
930 |
+ # change in the future. |
931 |
+ if not CONF.fake_network: |
932 |
+ interface_addrs = netifaces.ifaddresses(interface) |
933 |
+ interface_mac = interface_addrs[netifaces.AF_LINK][0]['addr'] |
934 |
+ _execute('ifconfig', bridge, 'ether', interface_mac, |
935 |
+ run_as_root=True) |
936 |
+ |
937 |
+ out, err = _execute('ifconfig', interface, 'up', |
938 |
+ check_exit_code=False, run_as_root=True) |
939 |
+ |
940 |
+ # NOTE(vish): This will break if there is already an ip on the |
941 |
+ # interface, so we move any ips to the bridge |
942 |
+ # NOTE(danms): We also need to copy routes to the bridge so as |
943 |
+ # not to break existing connectivity on the interface |
944 |
+ old_routes = [] |
945 |
+ out, err = _execute('netstat', '-nrW', '-f', 'inet') |
946 |
+ for line in out.split('\n'): |
947 |
+ fields = line.split() |
948 |
+ if len(fields) > 6 and (fields[6] == interface) and ('G' in fields[2]): |
949 |
+ old_routes.append(fields) |
950 |
+ _execute('route', '-q', 'delete', fields[0], fields[1], |
951 |
+ run_as_root=True) |
952 |
+ out, err = _execute('ifconfig', interface) |
953 |
+ for line in out.split('\n'): |
954 |
+ fields = line.split() |
955 |
+ if fields and fields[0] == 'inet': |
956 |
+ _execute(*_ifconfig_tail_cmd(interface, fields, 'delete'), |
957 |
+ run_as_root=True) |
958 |
+ _execute(*_ifconfig_tail_cmd(bridge, fields, 'add'), |
959 |
+ run_as_root=True) |
960 |
+ for fields in old_routes: |
961 |
+ _execute('route', '-q', 'add', fields[0], fields[1], |
962 |
+ run_as_root=True) |
963 |
+ |
964 |
+ if filtering: |
965 |
+ # Don't forward traffic unless we were told to be a gateway |
966 |
+ if gateway: |
967 |
+ firewall_manager.ensure_gateway_rules(bridge) |
968 |
+ else: |
969 |
+ firewall_manager.ensure_bridge_rules(bridge) |
970 |
+ |
971 |
+ @staticmethod |
972 |
+ @utils.synchronized('lock_bridge', external=True) |
973 |
+ def remove_bridge(bridge, gateway=True, filtering=True): |
974 |
+ """Delete a bridge.""" |
975 |
+ if not device_exists(bridge): |
976 |
+ return |
977 |
+ else: |
978 |
+ if filtering: |
979 |
+ if gateway: |
980 |
+ firewall_manager.remove_gateway_rules(bridge) |
981 |
+ else: |
982 |
+ firewall_manager.remove_bridge_rules(bridge) |
983 |
+ delete_bridge_dev(bridge) |
984 |
+ |
985 |
+ |
986 |
+def isolate_dhcp_address(interface, address): |
987 |
+ # block arp traffic to address across the interface |
988 |
+ firewall_manager.ensure_dhcp_isolation(interface, address) |
989 |
+ |
990 |
+ |
991 |
+def remove_isolate_dhcp_address(interface, address): |
992 |
+ # block arp traffic to address across the interface |
993 |
+ firewall_manager.remove_dhcp_isolation(interface, address) |
994 |
+ |
995 |
+ |
996 |
+# plugs interfaces using Open vSwitch |
997 |
+class FreeBSDOVSInterfaceDriver(FreeBSDNetInterfaceDriver): |
998 |
+ |
999 |
+ def plug(self, network, mac_address, gateway=True): |
1000 |
+ dev = self.get_dev(network) |
1001 |
+ if not device_exists(dev): |
1002 |
+ bridge = CONF.freebsdnet_ovs_integration_bridge |
1003 |
+ _ovs_vsctl(['--', '--may-exist', 'add-port', bridge, dev, |
1004 |
+ '--', 'set', 'Interface', dev, 'type=internal', |
1005 |
+ '--', 'set', 'Interface', dev, |
1006 |
+ 'external-ids:iface-id=%s' % dev, |
1007 |
+ '--', 'set', 'Interface', dev, |
1008 |
+ 'external-ids:iface-status=active', |
1009 |
+ '--', 'set', 'Interface', dev, |
1010 |
+ 'external-ids:attached-mac=%s' % mac_address]) |
1011 |
+ _execute('ifconfig', dev, 'ether', mac_address, run_as_root=True) |
1012 |
+ _set_device_mtu(dev, network.get('mtu')) |
1013 |
+ _execute('ifconfig', dev, 'up', run_as_root=True) |
1014 |
+ if not gateway: |
1015 |
+ # If we weren't instructed to act as a gateway then add the |
1016 |
+ # appropriate flows to block all non-dhcp traffic. |
1017 |
+ _execute('ovs-ofctl', |
1018 |
+ 'add-flow', bridge, 'priority=1,actions=drop', |
1019 |
+ run_as_root=True) |
1020 |
+ _execute('ovs-ofctl', 'add-flow', bridge, |
1021 |
+ 'udp,tp_dst=67,dl_dst=%s,priority=2,actions=normal' % |
1022 |
+ mac_address, run_as_root=True) |
1023 |
+ # .. and make sure iptbles won't forward it as well. |
1024 |
+ firewall_manager.ensure_bridge_rules(bridge) |
1025 |
+ else: |
1026 |
+ firewall_manager.ensure_gateway_rules(bridge) |
1027 |
+ |
1028 |
+ return dev |
1029 |
+ |
1030 |
+ def unplug(self, network): |
1031 |
+ dev = self.get_dev(network) |
1032 |
+ bridge = CONF.freebsdnet_ovs_integration_bridge |
1033 |
+ _ovs_vsctl(['--', '--if-exists', 'del-port', bridge, dev]) |
1034 |
+ return dev |
1035 |
+ |
1036 |
+ def get_dev(self, network): |
1037 |
+ dev = 'gw-' + str(network['uuid'][0:11]) |
1038 |
+ return dev |
1039 |
+ |
1040 |
+ |
1041 |
+# plugs interfaces using FreeBSD Bridge when using NeutronManager |
1042 |
+class NeutronFreeBSDBridgeInterfaceDriver(FreeBSDNetInterfaceDriver): |
1043 |
+ |
1044 |
+ BRIDGE_NAME_PREFIX = 'brq' |
1045 |
+ GATEWAY_INTERFACE_PREFIX = 'gw-' |
1046 |
+ |
1047 |
+ def plug(self, network, mac_address, gateway=True): |
1048 |
+ dev = self.get_dev(network) |
1049 |
+ bridge = self.get_bridge(network) |
1050 |
+ if not gateway: |
1051 |
+ # If we weren't instructed to act as a gateway then add the |
1052 |
+ # appropriate flows to block all non-dhcp traffic. |
1053 |
+ # .. and make sure iptbles won't forward it as well. |
1054 |
+ firewall_manager.ensure_bridge_rules(bridge) |
1055 |
+ return bridge |
1056 |
+ else: |
1057 |
+ firewall_manager.ensure_gateway_rules(bridge) |
1058 |
+ |
1059 |
+ create_tap_dev(dev, mac_address) |
1060 |
+ |
1061 |
+ if not device_exists(bridge): |
1062 |
+ LOG.debug("Starting bridge %s ", bridge) |
1063 |
+ utils.execute('ifconfig', 'bridge', 'create', 'name', bridge, run_as_root=True) |
1064 |
+ utils.execute('ifconfig', bridge, 'ether', mac_address, run_as_root=True) |
1065 |
+ utils.execute('ifconfig', bridge, 'up', run_as_root=True) |
1066 |
+ LOG.debug("Done starting bridge %s", bridge) |
1067 |
+ |
1068 |
+ full_ip = '%s/%s' % (network['dhcp_server'], |
1069 |
+ network['cidr'].rpartition('/')[2]) |
1070 |
+ utils.execute('ifconfig', bridge, full_ip, 'add', run_as_root=True) |
1071 |
+ |
1072 |
+ return dev |
1073 |
+ |
1074 |
+ def unplug(self, network): |
1075 |
+ dev = self.get_dev(network) |
1076 |
+ if not device_exists(dev): |
1077 |
+ return None |
1078 |
+ else: |
1079 |
+ delete_net_dev(dev) |
1080 |
+ return dev |
1081 |
+ |
1082 |
+ def get_dev(self, network): |
1083 |
+ dev = self.GATEWAY_INTERFACE_PREFIX + str(network['uuid'][0:11]) |
1084 |
+ return dev |
1085 |
+ |
1086 |
+ def get_bridge(self, network): |
1087 |
+ bridge = self.BRIDGE_NAME_PREFIX + str(network['uuid'][0:11]) |
1088 |
+ return bridge |
1089 |
+ |
1090 |
+ |
1091 |
+class FirewallManager(object): |
1092 |
+ def __init__(self, execute=_execute): |
1093 |
+ self.execute = execute |
1094 |
+ self.apply_deferred = False |
1095 |
+ self.anchor = 'org.openstack/%s' % get_binary_name() |
1096 |
+ self.rules = { |
1097 |
+ "translation": [], |
1098 |
+ "filtering": [] |
1099 |
+ } |
1100 |
+ self.is_dirty = False |
1101 |
+ |
1102 |
+ def _get_rule_section(self, rule): |
1103 |
+ LOG.warning("processing rule: %s" % rule) |
1104 |
+ head, tail = rule.split(' ', 1) |
1105 |
+ if head in ('nat', 'rdr'): |
1106 |
+ return 'translation' |
1107 |
+ elif head in ('pass', 'block'): |
1108 |
+ return 'filtering' |
1109 |
+ else: |
1110 |
+ return None |
1111 |
+ |
1112 |
+ def add_rule(self, rule): |
1113 |
+ cleaned_rule = rule.strip() |
1114 |
+ section = self._get_rule_section(cleaned_rule) |
1115 |
+ if section: |
1116 |
+ if cleaned_rule not in self.rules[section]: |
1117 |
+ self.rules[section].append(cleaned_rule) |
1118 |
+ self.is_dirty = True |
1119 |
+ LOG.warning("Added rule to %s: %s", section, cleaned_rule) |
1120 |
+ |
1121 |
+ def remove_rule(self, rule): |
1122 |
+ cleaned_rule = rule.strip() |
1123 |
+ section = self._get_rule_section(cleaned_rule) |
1124 |
+ LOG.warning("Removing rule from %s: %s", section, cleaned_rule) |
1125 |
+ if section: |
1126 |
+ try: |
1127 |
+ self.rules[section].remove(cleaned_rule) |
1128 |
+ self.is_dirty = True |
1129 |
+ except: |
1130 |
+ pass |
1131 |
+ |
1132 |
+ def defer_apply_on(self): |
1133 |
+ self.apply_deferred = True |
1134 |
+ |
1135 |
+ def defer_apply_off(self): |
1136 |
+ self.apply_deferred = False |
1137 |
+ self.apply() |
1138 |
+ |
1139 |
+ def dirty(self): |
1140 |
+ return self.is_dirty |
1141 |
+ |
1142 |
+ def apply(self): |
1143 |
+ if self.apply_deferred: |
1144 |
+ return |
1145 |
+ if self.dirty(): |
1146 |
+ self._apply() |
1147 |
+ else: |
1148 |
+ LOG.debug("Skipping apply due to lack of new rules") |
1149 |
+ |
1150 |
+ @utils.synchronized('pfctl', external=True) |
1151 |
+ def _apply(self): |
1152 |
+ all_lines = [] |
1153 |
+ all_lines.extend(self.rules['translation']) |
1154 |
+ all_lines.extend(self.rules['filtering']) |
1155 |
+ all_lines.extend(["\n"]) |
1156 |
+ |
1157 |
+ self.is_dirty = False |
1158 |
+ self.execute("pfctl", "-a", self.anchor, "-f", "-", |
1159 |
+ process_input="\n".join(all_lines), |
1160 |
+ run_as_root=True) |
1161 |
+ LOG.warning("FirewallManager.apply completed with success") |
1162 |
+ |
1163 |
+ def get_gateway_rules(self, bridge): |
1164 |
+ LOG.warning("FirewallManager.get_gateway_rules: " |
1165 |
+ "Please configure rules in pf.conf") |
1166 |
+ return [] |
1167 |
+ |
1168 |
+ def ensure_gateway_rules(self, bridge): |
1169 |
+ for rule in self.get_gateway_rules(bridge): |
1170 |
+ self.add_rule(rule) |
1171 |
+ |
1172 |
+ def remove_gateway_rules(self, bridge): |
1173 |
+ for rule in self.get_gateway_rules(bridge): |
1174 |
+ self.remove_rule(rule) |
1175 |
+ |
1176 |
+ def ensure_bridge_rules(self, bridge): |
1177 |
+ LOG.warning("FirewallManager.ensure_bridge_rules: " |
1178 |
+ "Please configure rules in pf.conf") |
1179 |
+ |
1180 |
+ def remove_bridge_rules(self, bridge): |
1181 |
+ LOG.warning("FirewallManager.remove_bridge_rules: " |
1182 |
+ "Please configure rules in pf.conf") |
1183 |
+ |
1184 |
+ def ensure_dhcp_isolation(self, interface, address): |
1185 |
+ LOG.warning("FirewallManager.ensure_dhcp_isolation: " |
1186 |
+ "DHCP isolation is not yet implemented") |
1187 |
+ |
1188 |
+ def remove_dhcp_isolation(self, interface, address): |
1189 |
+ LOG.warning("FirewallManager.remove_dhcp_isolation: " |
1190 |
+ "DHCP isolation is not yet implemented") |
1191 |
+ |
1192 |
+ def ensure_in_network_traffic_rules(self, fixed_ip, network): |
1193 |
+ LOG.warning("FirewallManager.ensure_in_network_traffic_rules: " |
1194 |
+ "Please configure rules in pf.conf") |
1195 |
+ |
1196 |
+ def remove_in_network_traffic_rules(self, fixed_ip, network): |
1197 |
+ LOG.warning("FirewallManager.remove_in_network_traffic_rules: " |
1198 |
+ "Please configure rules in pf.conf") |
1199 |
+ |
1200 |
+ def floating_forward_rules(self, floating_ip, fixed_ip, device): |
1201 |
+ rules = [] |
1202 |
+ rules.append("rdr inet from any to %s -> %s" % (floating_ip, fixed_ip)) |
1203 |
+ |
1204 |
+ return rules |
1205 |
+ |
1206 |
+ def ensure_floating_rules(self, floating_ip, fixed_ip, device): |
1207 |
+ for rule in self.floating_forward_rules(floating_ip, fixed_ip, device): |
1208 |
+ self.add_rule(rule) |
1209 |
+ |
1210 |
+ def remove_floating_rules(self, floating_ip, fixed_ip, device): |
1211 |
+ for rule in self.floating_forward_rules(floating_ip, fixed_ip, device): |
1212 |
+ self.remove_rule(rule) |
1213 |
+ |
1214 |
+ def add_snat_rule(self, ip_range, is_external=False): |
1215 |
+ if CONF.routing_source_ip: |
1216 |
+ if is_external: |
1217 |
+ if CONF.force_snat_range: |
1218 |
+ snat_range = CONF.force_snat_range |
1219 |
+ else: |
1220 |
+ snat_range = [] |
1221 |
+ else: |
1222 |
+ snat_range = ['0.0.0.0/0'] |
1223 |
+ for dest_range in snat_range: |
1224 |
+ if not is_external and CONF.public_interface: |
1225 |
+ firewall_manager.add_rule("nat on %s inet from %s to %s -> %s" % |
1226 |
+ (CONF.public_interface, |
1227 |
+ ip_range, |
1228 |
+ dest_range, |
1229 |
+ CONF.routing_source_ip)) |
1230 |
+ else: |
1231 |
+ firewall_manager.add_rule("nat inet from %s to %s -> %s" % |
1232 |
+ (ip_range, |
1233 |
+ dest_range, |
1234 |
+ CONF.routing_source_ip)) |
1235 |
+ firewall_manager.apply() |
1236 |
+ |
1237 |
+ |
1238 |
+firewall_manager = FirewallManager() |
1239 |
+ |
1240 |
+ |
1241 |
+def get_firewall_manager(): |
1242 |
+ return firewall_manager |
1243 |
-- |
1244 |
2.8.1 |
1245 |
|