1 # Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
2 # Copyright 2010 United States Government as represented by the
3 # Administrator of the National Aeronautics and Space Administration.
6 # Licensed under the Apache License, Version 2.0 (the "License"); you may
7 # not use this file except in compliance with the License. You may obtain
8 # a copy of the License at
10 # http://www.apache.org/licenses/LICENSE-2.0
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15 # License for the specific language governing permissions and limitations
18 """Implements vlans, bridges, and iptables rules using linux utilities."""
28 from oslo_concurrency import processutils
29 from oslo_config import cfg
30 from oslo_log import log as logging
31 from oslo_serialization import jsonutils
32 from oslo_utils import excutils
33 from oslo_utils import fileutils
34 from oslo_utils import importutils
35 from oslo_utils import timeutils
38 from nova import exception
39 from nova.i18n import _, _LE, _LW
40 from nova import objects
41 from nova import paths
42 from nova.pci import utils as pci_utils
43 from nova import utils
45 LOG = logging.getLogger(__name__)
49 cfg.MultiStrOpt('dhcpbridge_flagfile',
50 default=['/etc/nova/nova-dhcpbridge.conf'],
51 help='Location of flagfiles for dhcpbridge'),
52 cfg.StrOpt('networks_path',
53 default=paths.state_path_def('networks'),
54 help='Location to keep network config files'),
55 cfg.StrOpt('public_interface',
57 help='Interface for public IP addresses'),
58 cfg.StrOpt('dhcpbridge',
59 default=paths.bindir_def('nova-dhcpbridge'),
60 help='Location of nova-dhcpbridge'),
61 cfg.StrOpt('routing_source_ip',
63 help='Public IP of network host'),
64 cfg.IntOpt('dhcp_lease_time',
66 help='Lifetime of a DHCP lease in seconds'),
67 cfg.MultiStrOpt('dns_server',
69 help='If set, uses specific DNS server for dnsmasq. Can'
70 ' be specified multiple times.'),
71 cfg.BoolOpt('use_network_dns_servers',
73 help='If set, uses the dns1 and dns2 from the network ref.'
75 cfg.ListOpt('dmz_cidr',
77 help='A list of dmz ranges that should be accepted'),
78 cfg.MultiStrOpt('force_snat_range',
80 help='Traffic to this range will always be snatted to the '
81 'fallback ip, even if it would normally be bridged out '
82 'of the node. Can be specified multiple times.'),
83 cfg.StrOpt('dnsmasq_config_file',
85 help='Override the default dnsmasq settings with this file'),
86 cfg.StrOpt('linuxnet_interface_driver',
87 default='nova.network.linux_net.LinuxBridgeInterfaceDriver',
88 help='Driver used to create ethernet devices.'),
89 cfg.StrOpt('linuxnet_ovs_integration_bridge',
91 help='Name of Open vSwitch bridge used with linuxnet'),
92 cfg.BoolOpt('send_arp_for_ha',
94 help='Send gratuitous ARPs for HA setup'),
95 cfg.IntOpt('send_arp_for_ha_count',
97 help='Send this many gratuitous ARPs for HA setup'),
98 cfg.BoolOpt('use_single_default_gateway',
100 help='Use single default gateway. Only first nic of vm will '
101 'get default gateway from dhcp server'),
102 cfg.MultiStrOpt('forward_bridge_interface',
104 help='An interface that bridges can forward to. If this '
105 'is set to all then all traffic will be forwarded. '
106 'Can be specified multiple times.'),
107 cfg.StrOpt('metadata_host',
109 help='The IP address for the metadata API server'),
110 cfg.IntOpt('metadata_port',
114 help='The port for the metadata API port'),
115 cfg.StrOpt('iptables_top_regex',
117 help='Regular expression to match the iptables rule that '
118 'should always be on the top.'),
119 cfg.StrOpt('iptables_bottom_regex',
121 help='Regular expression to match the iptables rule that '
122 'should always be on the bottom.'),
123 cfg.StrOpt('iptables_drop_action',
125 help='The table that iptables to jump to when a packet is '
127 cfg.IntOpt('ovs_vsctl_timeout',
129 help='Amount of time, in seconds, that ovs_vsctl should wait '
130 'for a response from the database. 0 is to wait forever.'),
131 cfg.BoolOpt('fake_network',
133 help='If passed, use fake network devices and addresses'),
134 cfg.IntOpt('ebtables_exec_attempts',
136 help='Number of times to retry ebtables commands on failure.'),
137 cfg.FloatOpt('ebtables_retry_interval',
139 help='Number of seconds to wait between ebtables retries.'),
143 CONF.register_opts(linux_net_opts)
144 CONF.import_opt('host', 'nova.netconf')
145 CONF.import_opt('use_ipv6', 'nova.netconf')
146 CONF.import_opt('my_ip', 'nova.netconf')
147 CONF.import_opt('network_device_mtu', 'nova.objects.network')
150 # NOTE(vish): Iptables supports chain names of up to 28 characters, and we
151 # add up to 12 characters to binary_name which is used as a prefix,
152 # so we limit it to 16 characters.
153 # (max_chain_name_length - len('-POSTROUTING') == 16)
154 def get_binary_name():
155 """Grab the name of the binary we're running in."""
156 return os.path.basename(inspect.stack()[-1][1])[:16]
158 binary_name = get_binary_name()
161 class IptablesRule(object):
164 You shouldn't need to use this class directly, it's only used by
169 def __init__(self, chain, rule, wrap=True, top=False):
175 def __eq__(self, other):
176 return ((self.chain == other.chain) and
177 (self.rule == other.rule) and
178 (self.top == other.top) and
179 (self.wrap == other.wrap))
181 def __ne__(self, other):
182 return not self == other
186 chain = '%s-%s' % (binary_name, self.chain)
189 # new rules should have a zero [packet: byte] count
190 return '[0:0] -A %s %s' % (chain, self.rule)
193 class IptablesTable(object):
194 """An iptables table."""
198 self.remove_rules = []
200 self.unwrapped_chains = set()
201 self.remove_chains = set()
204 def has_chain(self, name, wrap=True):
206 return name in self.chains
208 return name in self.unwrapped_chains
210 def add_chain(self, name, wrap=True):
211 """Adds a named chain to the table.
213 The chain name is wrapped to be unique for the component creating
214 it, so different components of Nova can safely create identically
215 named chains without interfering with one another.
217 At the moment, its wrapped name is <binary name>-<chain name>,
218 so if nova-compute creates a chain named 'OUTPUT', it'll actually
219 end up named 'nova-compute-OUTPUT'.
223 self.chains.add(name)
225 self.unwrapped_chains.add(name)
228 def remove_chain(self, name, wrap=True):
229 """Remove named chain.
231 This removal "cascades". All rule in the chain are removed, as are
232 all rules in other chains that jump to it.
234 If the chain is not found, this is merely logged.
238 chain_set = self.chains
240 chain_set = self.unwrapped_chains
242 if name not in chain_set:
243 LOG.warning(_LW('Attempted to remove chain %s which does not '
248 # non-wrapped chains and rules need to be dealt with specially,
249 # so we keep a list of them to be iterated over in apply()
251 self.remove_chains.add(name)
252 chain_set.remove(name)
254 self.remove_rules += filter(lambda r: r.chain == name, self.rules)
255 self.rules = filter(lambda r: r.chain != name, self.rules)
258 jump_snippet = '-j %s-%s' % (binary_name, name)
260 jump_snippet = '-j %s' % (name,)
263 self.remove_rules += filter(lambda r: jump_snippet in r.rule,
265 self.rules = filter(lambda r: jump_snippet not in r.rule, self.rules)
267 def add_rule(self, chain, rule, wrap=True, top=False):
268 """Add a rule to the table.
270 This is just like what you'd feed to iptables, just without
271 the '-A <chain name>' bit at the start.
273 However, if you need to jump to one of your wrapped chains,
274 prepend its name with a '$' which will ensure the wrapping
275 is applied correctly.
278 if wrap and chain not in self.chains:
279 raise ValueError(_('Unknown chain: %r') % chain)
282 rule = ' '.join(map(self._wrap_target_chain, rule.split(' ')))
284 rule_obj = IptablesRule(chain, rule, wrap, top)
285 if rule_obj in self.rules:
286 LOG.debug("Skipping duplicate iptables rule addition. "
287 "%(rule)r already in %(rules)r",
288 {'rule': rule_obj, 'rules': self.rules})
290 self.rules.append(IptablesRule(chain, rule, wrap, top))
293 def _wrap_target_chain(self, s):
294 if s.startswith('$'):
295 return '%s-%s' % (binary_name, s[1:])
298 def remove_rule(self, chain, rule, wrap=True, top=False):
299 """Remove a rule from a chain.
301 Note: The rule must be exactly identical to the one that was added.
302 You cannot switch arguments around like you can with the iptables
307 self.rules.remove(IptablesRule(chain, rule, wrap, top))
309 self.remove_rules.append(IptablesRule(chain, rule, wrap, top))
312 LOG.warning(_LW('Tried to remove rule that was not there:'
313 ' %(chain)r %(rule)r %(wrap)r %(top)r'),
314 {'chain': chain, 'rule': rule,
315 'top': top, 'wrap': wrap})
317 def remove_rules_regex(self, regex):
318 """Remove all rules matching regex."""
319 if isinstance(regex, six.string_types):
320 regex = re.compile(regex)
321 num_rules = len(self.rules)
322 self.rules = filter(lambda r: not regex.match(str(r)), self.rules)
323 removed = num_rules - len(self.rules)
328 def empty_chain(self, chain, wrap=True):
329 """Remove all rules from a chain."""
330 chained_rules = [rule for rule in self.rules
331 if rule.chain == chain and rule.wrap == wrap]
334 for rule in chained_rules:
335 self.rules.remove(rule)
338 class IptablesManager(object):
339 """Wrapper for iptables.
341 See IptablesTable for some usage docs
343 A number of chains are set up to begin with.
345 First, nova-filter-top. It's added at the top of FORWARD and OUTPUT. Its
346 name is not wrapped, so it's shared between the various nova workers. It's
347 intended for rules that need to live at the top of the FORWARD and OUTPUT
348 chains. It's in both the ipv4 and ipv6 set of tables.
350 For ipv4 and ipv6, the built-in INPUT, OUTPUT, and FORWARD filter chains
351 are wrapped, meaning that the "real" INPUT chain has a rule that jumps to
352 the wrapped INPUT chain, etc. Additionally, there's a wrapped chain named
353 "local" which is jumped to from nova-filter-top.
355 For ipv4, the built-in PREROUTING, OUTPUT, and POSTROUTING nat chains are
356 wrapped in the same was as the built-in filter chains. Additionally,
357 there's a snat chain that is applied after the POSTROUTING chain.
361 def __init__(self, execute=None):
363 self.execute = _execute
365 self.execute = execute
367 self.ipv4 = {'filter': IptablesTable(),
368 'nat': IptablesTable(),
369 'mangle': IptablesTable()}
370 self.ipv6 = {'filter': IptablesTable()}
372 self.iptables_apply_deferred = False
374 # Add a nova-filter-top chain. It's intended to be shared
375 # among the various nova components. It sits at the very top
376 # of FORWARD and OUTPUT.
377 for tables in [self.ipv4, self.ipv6]:
378 tables['filter'].add_chain('nova-filter-top', wrap=False)
379 tables['filter'].add_rule('FORWARD', '-j nova-filter-top',
380 wrap=False, top=True)
381 tables['filter'].add_rule('OUTPUT', '-j nova-filter-top',
382 wrap=False, top=True)
384 tables['filter'].add_chain('local')
385 tables['filter'].add_rule('nova-filter-top', '-j $local',
388 # Wrap the built-in chains
389 builtin_chains = {4: {'filter': ['INPUT', 'OUTPUT', 'FORWARD'],
390 'nat': ['PREROUTING', 'OUTPUT', 'POSTROUTING'],
391 'mangle': ['POSTROUTING']},
392 6: {'filter': ['INPUT', 'OUTPUT', 'FORWARD']}}
394 for ip_version in builtin_chains:
397 elif ip_version == 6:
400 for table, chains in six.iteritems(builtin_chains[ip_version]):
402 tables[table].add_chain(chain)
403 tables[table].add_rule(chain, '-j $%s' % (chain,),
406 # Add a nova-postrouting-bottom chain. It's intended to be shared
407 # among the various nova components. We set it as the last chain
408 # of POSTROUTING chain.
409 self.ipv4['nat'].add_chain('nova-postrouting-bottom', wrap=False)
410 self.ipv4['nat'].add_rule('POSTROUTING', '-j nova-postrouting-bottom',
413 # We add a snat chain to the shared nova-postrouting-bottom chain
414 # so that it's applied last.
415 self.ipv4['nat'].add_chain('snat')
416 self.ipv4['nat'].add_rule('nova-postrouting-bottom', '-j $snat',
419 # And then we add a float-snat chain and jump to first thing in
421 self.ipv4['nat'].add_chain('float-snat')
422 self.ipv4['nat'].add_rule('snat', '-j $float-snat')
424 def defer_apply_on(self):
425 self.iptables_apply_deferred = True
427 def defer_apply_off(self):
428 self.iptables_apply_deferred = False
432 for table in six.itervalues(self.ipv4):
436 for table in six.itervalues(self.ipv6):
442 if self.iptables_apply_deferred:
447 LOG.debug("Skipping apply due to lack of new rules")
449 @utils.synchronized('iptables', external=True)
451 """Apply the current in-memory set of iptables rules.
453 This will blow away any rules left over from previous runs of the
454 same component of Nova, and replace them with our current set of
455 rules. This happens atomically, thanks to iptables-restore.
458 s = [('iptables', self.ipv4)]
460 s += [('ip6tables', self.ipv6)]
462 for cmd, tables in s:
463 all_tables, _err = self.execute('%s-save' % (cmd,), '-c',
466 all_lines = all_tables.split('\n')
467 for table_name, table in six.iteritems(tables):
468 start, end = self._find_table(all_lines, table_name)
469 all_lines[start:end] = self._modify_rules(
470 all_lines[start:end], table, table_name)
472 self.execute('%s-restore' % (cmd,), '-c', run_as_root=True,
473 process_input='\n'.join(all_lines),
475 LOG.debug("IPTablesManager.apply completed with success")
477 def _find_table(self, lines, table_name):
479 # length only <2 when fake iptables
482 start = lines.index('*%s' % table_name) - 1
484 # Couldn't find table_name
486 end = lines[start:].index('COMMIT') + start + 2
489 def _modify_rules(self, current_lines, table, table_name):
490 unwrapped_chains = table.unwrapped_chains
491 chains = sorted(table.chains)
492 remove_chains = table.remove_chains
494 remove_rules = table.remove_rules
496 if not current_lines:
497 fake_table = ['#Generated by nova',
498 '*' + table_name, 'COMMIT',
499 '#Completed by nova']
500 current_lines = fake_table
502 # Remove any trace of our rules
503 new_filter = filter(lambda line: binary_name not in line,
509 if CONF.iptables_top_regex:
510 regex = re.compile(CONF.iptables_top_regex)
511 temp_filter = filter(lambda line: regex.search(line), new_filter)
512 for rule_str in temp_filter:
513 new_filter = filter(lambda s: s.strip() != rule_str.strip(),
515 top_rules = temp_filter
517 if CONF.iptables_bottom_regex:
518 regex = re.compile(CONF.iptables_bottom_regex)
519 temp_filter = filter(lambda line: regex.search(line), new_filter)
520 for rule_str in temp_filter:
521 new_filter = filter(lambda s: s.strip() != rule_str.strip(),
523 bottom_rules = temp_filter
527 for rules_index, rule in enumerate(new_filter):
529 if rule.startswith(':'):
532 if not rule.startswith(':'):
538 our_rules = top_rules
543 # rule.top == True means we want this rule to be at the top.
544 # Further down, we weed out duplicates from the bottom of the
545 # list, so here we remove the dupes ahead of time.
547 # We don't want to remove an entry if it has non-zero
548 # [packet:byte] counts and replace it with [0:0], so let's
549 # go look for a duplicate, and over-ride our table rule if
552 # ignore [packet:byte] counts at beginning of line
553 if rule_str.startswith('['):
554 rule_str = rule_str.split(']', 1)[1]
555 dup_filter = filter(lambda s: rule_str.strip() in s.strip(),
558 new_filter = filter(lambda s:
559 rule_str.strip() not in s.strip(),
561 # if no duplicates, use original rule
563 # grab the last entry, if there is one
570 our_rules += [rule_str]
572 bot_rules += [rule_str]
574 our_rules += bot_rules
576 new_filter[rules_index:rules_index] = our_rules
578 new_filter[rules_index:rules_index] = [':%s - [0:0]' % (name,)
579 for name in unwrapped_chains]
580 new_filter[rules_index:rules_index] = [':%s-%s - [0:0]' %
584 commit_index = new_filter.index('COMMIT')
585 new_filter[commit_index:commit_index] = bottom_rules
588 def _weed_out_duplicates(line):
589 # ignore [packet:byte] counts at beginning of lines
590 if line.startswith('['):
591 line = line.split(']', 1)[1]
593 if line in seen_lines:
599 def _weed_out_removes(line):
600 # We need to find exact matches here
601 if line.startswith(':'):
602 # it's a chain, for example, ":nova-billing - [0:0]"
603 # strip off everything except the chain name
604 line = line.split(':')[1]
605 line = line.split('- [')[0]
607 for chain in remove_chains:
609 remove_chains.remove(chain)
611 elif line.startswith('['):
613 # ignore [packet:byte] counts at beginning of lines
614 line = line.split(']', 1)[1]
616 for rule in remove_rules:
617 # ignore [packet:byte] counts at beginning of rules
619 rule_str = rule_str.split(' ', 1)[1]
620 rule_str = rule_str.strip()
622 remove_rules.remove(rule)
628 # We filter duplicates, letting the *last* occurrence take
629 # precedence. We also filter out anything in the "remove"
632 new_filter = filter(_weed_out_duplicates, new_filter)
633 new_filter = filter(_weed_out_removes, new_filter)
636 # flush lists, just in case we didn't find something
637 remove_chains.clear()
638 for rule in remove_rules:
639 remove_rules.remove(rule)
644 # NOTE(jkoelker) This is just a nice little stub point since mocking
645 # builtins with mox is a nightmare
646 def write_to_file(file, data, mode='w'):
647 with open(file, mode) as f:
651 def is_pid_cmdline_correct(pid, match):
652 """Ensure that the cmdline for a pid seems sane
654 Because pids are recycled, blindly killing by pid is something to
655 avoid. This provides the ability to include a substring that is
656 expected in the cmdline as a safety check.
659 with open('/proc/%d/cmdline' % pid) as f:
661 return match in cmdline
662 except EnvironmentError:
666 def metadata_forward():
667 """Create forwarding rule for metadata."""
668 if CONF.metadata_host != '127.0.0.1':
669 iptables_manager.ipv4['nat'].add_rule('PREROUTING',
670 '-s 0.0.0.0/0 -d 169.254.169.254/32 '
671 '-p tcp -m tcp --dport 80 -j DNAT '
672 '--to-destination %s:%s' %
676 iptables_manager.ipv4['nat'].add_rule('PREROUTING',
677 '-s 0.0.0.0/0 -d 169.254.169.254/32 '
678 '-p tcp -m tcp --dport 80 '
679 '-j REDIRECT --to-ports %s' %
681 iptables_manager.apply()
684 def _iptables_dest(ip):
685 if ((netaddr.IPAddress(ip).version == 4 and ip == '127.0.0.1')
687 return '-m addrtype --dst-type LOCAL'
692 def metadata_accept():
693 """Create the filter accept rule for metadata."""
695 rule = ('-p tcp -m tcp --dport %s %s -j ACCEPT' %
696 (CONF.metadata_port, _iptables_dest(CONF.metadata_host)))
698 if netaddr.IPAddress(CONF.metadata_host).version == 4:
699 iptables_manager.ipv4['filter'].add_rule('INPUT', rule)
701 iptables_manager.ipv6['filter'].add_rule('INPUT', rule)
703 iptables_manager.apply()
706 def add_snat_rule(ip_range, is_external=False):
707 if CONF.routing_source_ip:
709 if CONF.force_snat_range:
710 snat_range = CONF.force_snat_range
714 snat_range = ['0.0.0.0/0']
715 for dest_range in snat_range:
716 rule = ('-s %s -d %s -j SNAT --to-source %s'
717 % (ip_range, dest_range, CONF.routing_source_ip))
718 if not is_external and CONF.public_interface:
719 rule += ' -o %s' % CONF.public_interface
720 iptables_manager.ipv4['nat'].add_rule('snat', rule)
721 iptables_manager.apply()
724 def init_host(ip_range, is_external=False):
725 """Basic networking setup goes here."""
726 # NOTE(devcamcar): Cloud public SNAT entries and the default
727 # SNAT rule for outbound traffic.
729 add_snat_rule(ip_range, is_external)
733 for snat_range in CONF.force_snat_range:
734 rules.append('PREROUTING -p ipv4 --ip-src %s --ip-dst %s '
735 '-j redirect --redirect-target ACCEPT' %
736 (ip_range, snat_range))
738 ensure_ebtables_rules(rules, 'nat')
740 iptables_manager.ipv4['nat'].add_rule('POSTROUTING',
741 '-s %s -d %s/32 -j ACCEPT' %
742 (ip_range, CONF.metadata_host))
744 for dmz in CONF.dmz_cidr:
745 iptables_manager.ipv4['nat'].add_rule('POSTROUTING',
746 '-s %s -d %s -j ACCEPT' %
749 iptables_manager.ipv4['nat'].add_rule('POSTROUTING',
750 '-s %(range)s -d %(range)s '
751 '-m conntrack ! --ctstate DNAT '
754 iptables_manager.apply()
757 def send_arp_for_ip(ip, device, count):
758 out, err = _execute('arping', '-U', ip,
761 run_as_root=True, check_exit_code=False)
764 LOG.debug('arping error for ip %s', ip)
767 def bind_floating_ip(floating_ip, device):
768 """Bind ip to public interface."""
769 _execute('ip', 'addr', 'add', str(floating_ip) + '/32',
771 run_as_root=True, check_exit_code=[0, 2, 254])
773 if CONF.send_arp_for_ha and CONF.send_arp_for_ha_count > 0:
774 send_arp_for_ip(floating_ip, device, CONF.send_arp_for_ha_count)
777 def unbind_floating_ip(floating_ip, device):
778 """Unbind a public ip from public interface."""
779 _execute('ip', 'addr', 'del', str(floating_ip) + '/32',
781 run_as_root=True, check_exit_code=[0, 2, 254])
784 def ensure_metadata_ip():
785 """Sets up local metadata ip."""
786 _execute('ip', 'addr', 'add', '169.254.169.254/32',
787 'scope', 'link', 'dev', 'lo',
788 run_as_root=True, check_exit_code=[0, 2, 254])
791 def ensure_vpn_forward(public_ip, port, private_ip):
792 """Sets up forwarding rules for vlan."""
793 iptables_manager.ipv4['filter'].add_rule('FORWARD',
796 '-j ACCEPT' % private_ip)
797 iptables_manager.ipv4['nat'].add_rule('PREROUTING',
799 '--dport %s -j DNAT --to %s:1194' %
800 (public_ip, port, private_ip))
801 iptables_manager.ipv4['nat'].add_rule('OUTPUT',
803 '--dport %s -j DNAT --to %s:1194' %
804 (public_ip, port, private_ip))
805 iptables_manager.apply()
808 def ensure_floating_forward(floating_ip, fixed_ip, device, network):
809 """Ensure floating ip forwarding rule."""
810 # NOTE(vish): Make sure we never have duplicate rules for the same ip
811 regex = '.*\s+%s(/32|\s+|$)' % floating_ip
812 num_rules = iptables_manager.ipv4['nat'].remove_rules_regex(regex)
814 msg = _LW('Removed %(num)d duplicate rules for floating ip %(float)s')
815 LOG.warn(msg, {'num': num_rules, 'float': floating_ip})
816 for chain, rule in floating_forward_rules(floating_ip, fixed_ip, device):
817 iptables_manager.ipv4['nat'].add_rule(chain, rule)
818 iptables_manager.apply()
819 if device != network['bridge']:
820 ensure_ebtables_rules(*floating_ebtables_rules(fixed_ip, network))
823 def remove_floating_forward(floating_ip, fixed_ip, device, network):
824 """Remove forwarding for floating ip."""
825 for chain, rule in floating_forward_rules(floating_ip, fixed_ip, device):
826 iptables_manager.ipv4['nat'].remove_rule(chain, rule)
827 iptables_manager.apply()
828 if device != network['bridge']:
829 remove_ebtables_rules(*floating_ebtables_rules(fixed_ip, network))
832 def floating_ebtables_rules(fixed_ip, network):
833 """Makes sure only in-network traffic is bridged."""
834 return (['PREROUTING --logical-in %s -p ipv4 --ip-src %s '
835 '! --ip-dst %s -j redirect --redirect-target ACCEPT' %
836 (network['bridge'], fixed_ip, network['cidr'])], 'nat')
839 def floating_forward_rules(floating_ip, fixed_ip, device):
841 rule = '-s %s -j SNAT --to %s' % (fixed_ip, floating_ip)
843 rules.append(('float-snat', rule + ' -d %s' % fixed_ip))
844 rules.append(('float-snat', rule + ' -o %s' % device))
846 rules.append(('float-snat', rule))
848 ('PREROUTING', '-d %s -j DNAT --to %s' % (floating_ip, fixed_ip)))
850 ('OUTPUT', '-d %s -j DNAT --to %s' % (floating_ip, fixed_ip)))
851 rules.append(('POSTROUTING', '-s %s -m conntrack --ctstate DNAT -j SNAT '
853 (fixed_ip, floating_ip)))
857 def clean_conntrack(fixed_ip):
859 _execute('conntrack', '-D', '-r', fixed_ip, run_as_root=True,
860 check_exit_code=[0, 1])
861 except processutils.ProcessExecutionError:
862 LOG.exception(_LE('Error deleting conntrack entries for %s'), fixed_ip)
865 def _enable_ipv4_forwarding():
866 sysctl_key = 'net.ipv4.ip_forward'
867 stdout, stderr = _execute('sysctl', '-n', sysctl_key)
868 if stdout.strip() is not '1':
869 _execute('sysctl', '-w', '%s=1' % sysctl_key, run_as_root=True)
872 @utils.synchronized('lock_gateway', external=True)
873 def initialize_gateway_device(dev, network_ref):
877 _enable_ipv4_forwarding()
879 # NOTE(vish): The ip for dnsmasq has to be the first address on the
880 # bridge for it to respond to requests properly
882 prefix = network_ref.cidr.prefixlen
883 except AttributeError:
884 prefix = network_ref['cidr'].rpartition('/')[2]
886 full_ip = '%s/%s' % (network_ref['dhcp_server'], prefix)
887 new_ip_params = [[full_ip, 'brd', network_ref['broadcast']]]
889 out, err = _execute('ip', 'addr', 'show', 'dev', dev,
891 for line in out.split('\n'):
892 fields = line.split()
893 if fields and fields[0] == 'inet':
894 if fields[-2] in ('secondary', 'dynamic'):
895 ip_params = fields[1:-2]
897 ip_params = fields[1:-1]
898 old_ip_params.append(ip_params)
899 if ip_params[0] != full_ip:
900 new_ip_params.append(ip_params)
901 if not old_ip_params or old_ip_params[0][0] != full_ip:
903 result = _execute('ip', 'route', 'show', 'dev', dev)
906 for line in out.split('\n'):
907 fields = line.split()
908 if fields and 'via' in fields:
909 old_routes.append(fields)
910 _execute('ip', 'route', 'del', fields[0],
911 'dev', dev, run_as_root=True)
912 for ip_params in old_ip_params:
913 _execute(*_ip_bridge_cmd('del', ip_params, dev),
914 run_as_root=True, check_exit_code=[0, 2, 254])
915 for ip_params in new_ip_params:
916 _execute(*_ip_bridge_cmd('add', ip_params, dev),
917 run_as_root=True, check_exit_code=[0, 2, 254])
919 for fields in old_routes:
920 _execute('ip', 'route', 'add', *fields,
922 if CONF.send_arp_for_ha and CONF.send_arp_for_ha_count > 0:
923 send_arp_for_ip(network_ref['dhcp_server'], dev,
924 CONF.send_arp_for_ha_count)
926 _execute('ip', '-f', 'inet6', 'addr',
927 'change', network_ref['cidr_v6'],
928 'dev', dev, run_as_root=True)
931 def get_dhcp_leases(context, network_ref):
932 """Return a network's hosts config in dnsmasq leasefile format."""
935 if network_ref['multi_host']:
937 for fixedip in objects.FixedIPList.get_by_network(context,
940 # NOTE(cfb): Don't return a lease entry if the IP isn't
943 hosts.append(_host_lease(fixedip))
945 return '\n'.join(hosts)
948 def get_dhcp_hosts(context, network_ref, fixedips):
949 """Get network's hosts config in dhcp-host format."""
952 for fixedip in fixedips:
953 if fixedip.allocated:
954 if fixedip.virtual_interface.address not in macs:
955 hosts.append(_host_dhcp(fixedip))
956 macs.add(fixedip.virtual_interface.address)
957 return '\n'.join(hosts)
960 def get_dns_hosts(context, network_ref):
961 """Get network's DNS hosts in hosts format."""
963 for fixedip in objects.FixedIPList.get_by_network(context, network_ref):
964 if fixedip.allocated:
965 hosts.append(_host_dns(fixedip))
966 return '\n'.join(hosts)
969 def _add_dnsmasq_accept_rules(dev):
970 """Allow DHCP and DNS traffic through to dnsmasq."""
971 table = iptables_manager.ipv4['filter']
972 for port in [67, 53]:
973 for proto in ['udp', 'tcp']:
974 args = {'dev': dev, 'port': port, 'proto': proto}
975 table.add_rule('INPUT',
976 '-i %(dev)s -p %(proto)s -m %(proto)s '
977 '--dport %(port)s -j ACCEPT' % args)
978 iptables_manager.apply()
981 def _remove_dnsmasq_accept_rules(dev):
982 """Remove DHCP and DNS traffic allowed through to dnsmasq."""
983 table = iptables_manager.ipv4['filter']
984 for port in [67, 53]:
985 for proto in ['udp', 'tcp']:
986 args = {'dev': dev, 'port': port, 'proto': proto}
987 table.remove_rule('INPUT',
988 '-i %(dev)s -p %(proto)s -m %(proto)s '
989 '--dport %(port)s -j ACCEPT' % args)
990 iptables_manager.apply()
993 # NOTE(russellb) Curious why this is needed? Check out this explanation from
994 # markmc: https://bugzilla.redhat.com/show_bug.cgi?id=910619#c6
995 def _add_dhcp_mangle_rule(dev):
996 table = iptables_manager.ipv4['mangle']
997 table.add_rule('POSTROUTING',
998 '-o %s -p udp -m udp --dport 68 -j CHECKSUM '
999 '--checksum-fill' % dev)
1000 iptables_manager.apply()
1003 def _remove_dhcp_mangle_rule(dev):
1004 table = iptables_manager.ipv4['mangle']
1005 table.remove_rule('POSTROUTING',
1006 '-o %s -p udp -m udp --dport 68 -j CHECKSUM '
1007 '--checksum-fill' % dev)
1008 iptables_manager.apply()
1011 def get_dhcp_opts(context, network_ref, fixedips):
1012 """Get network's hosts config in dhcp-opts format."""
1013 gateway = network_ref['gateway']
1014 # NOTE(vish): if we are in multi-host mode and we are not sharing
1015 # addresses, then we actually need to hand out the
1016 # dhcp server address as the gateway.
1017 if network_ref['multi_host'] and not (network_ref['share_address'] or
1018 CONF.share_dhcp_address):
1019 gateway = network_ref['dhcp_server']
1021 if CONF.use_single_default_gateway:
1022 for fixedip in fixedips:
1023 if fixedip.allocated:
1024 vif_id = fixedip.virtual_interface_id
1025 if fixedip.default_route:
1026 hosts.append(_host_dhcp_opts(vif_id, gateway))
1028 hosts.append(_host_dhcp_opts(vif_id))
1030 hosts.append(_host_dhcp_opts(None, gateway))
1031 return '\n'.join(hosts)
1034 def release_dhcp(dev, address, mac_address):
1035 if device_exists(dev):
1037 utils.execute('dhcp_release', dev, address, mac_address,
1039 except processutils.ProcessExecutionError:
1040 raise exception.NetworkDhcpReleaseFailed(address=address,
1041 mac_address=mac_address)
1044 def update_dhcp(context, dev, network_ref):
1045 conffile = _dhcp_file(dev, 'conf')
1047 if network_ref['multi_host']:
1049 fixedips = objects.FixedIPList.get_by_network(context,
1052 write_to_file(conffile, get_dhcp_hosts(context, network_ref, fixedips))
1053 restart_dhcp(context, dev, network_ref, fixedips)
1056 def update_dns(context, dev, network_ref):
1057 hostsfile = _dhcp_file(dev, 'hosts')
1059 if network_ref['multi_host']:
1061 fixedips = objects.FixedIPList.get_by_network(context,
1064 write_to_file(hostsfile, get_dns_hosts(context, network_ref))
1065 restart_dhcp(context, dev, network_ref, fixedips)
1068 def update_dhcp_hostfile_with_text(dev, hosts_text):
1069 conffile = _dhcp_file(dev, 'conf')
1070 write_to_file(conffile, hosts_text)
1074 pid = _dnsmasq_pid_for(dev)
1076 # Check that the process exists and looks like a dnsmasq process
1077 conffile = _dhcp_file(dev, 'conf')
1078 if is_pid_cmdline_correct(pid, conffile.split('/')[-1]):
1079 _execute('kill', '-9', pid, run_as_root=True)
1081 LOG.debug('Pid %d is stale, skip killing dnsmasq', pid)
1082 _remove_dnsmasq_accept_rules(dev)
1083 _remove_dhcp_mangle_rule(dev)
1086 # NOTE(ja): Sending a HUP only reloads the hostfile, so any
1087 # configuration options (like dchp-range, vlan, ...)
1089 @utils.synchronized('dnsmasq_start')
1090 def restart_dhcp(context, dev, network_ref, fixedips):
1091 """(Re)starts a dnsmasq server for a given network.
1093 If a dnsmasq instance is already running then send a HUP
1094 signal causing it to reload, otherwise spawn a new instance.
1097 conffile = _dhcp_file(dev, 'conf')
1099 optsfile = _dhcp_file(dev, 'opts')
1100 write_to_file(optsfile, get_dhcp_opts(context, network_ref, fixedips))
1101 os.chmod(optsfile, 0o644)
1103 _add_dhcp_mangle_rule(dev)
1105 # Make sure dnsmasq can actually read it (it setuid()s to "nobody")
1106 os.chmod(conffile, 0o644)
1108 pid = _dnsmasq_pid_for(dev)
1110 # if dnsmasq is already running, then tell it to reload
1112 if is_pid_cmdline_correct(pid, conffile.split('/')[-1]):
1114 _execute('kill', '-HUP', pid, run_as_root=True)
1115 _add_dnsmasq_accept_rules(dev)
1117 except Exception as exc:
1118 LOG.error(_LE('kill -HUP dnsmasq threw %s'), exc)
1120 LOG.debug('Pid %d is stale, relaunching dnsmasq', pid)
1123 'CONFIG_FILE=%s' % jsonutils.dumps(CONF.dhcpbridge_flagfile),
1124 'NETWORK_ID=%s' % str(network_ref['id']),
1127 '--bind-interfaces',
1128 '--conf-file=%s' % CONF.dnsmasq_config_file,
1129 '--pid-file=%s' % _dhcp_file(dev, 'pid'),
1130 '--dhcp-optsfile=%s' % _dhcp_file(dev, 'opts'),
1131 '--listen-address=%s' % network_ref['dhcp_server'],
1132 '--except-interface=lo',
1133 '--dhcp-range=set:%s,%s,static,%s,%ss' %
1134 (network_ref['label'],
1135 network_ref['dhcp_start'],
1136 network_ref['netmask'],
1137 CONF.dhcp_lease_time),
1138 '--dhcp-lease-max=%s' % len(netaddr.IPNetwork(network_ref['cidr'])),
1139 '--dhcp-hostsfile=%s' % _dhcp_file(dev, 'conf'),
1140 '--dhcp-script=%s' % CONF.dhcpbridge,
1144 # dnsmasq currently gives an error for an empty domain,
1145 # rather than ignoring. So only specify it if defined.
1146 if CONF.dhcp_domain:
1147 cmd.append('--domain=%s' % CONF.dhcp_domain)
1149 dns_servers = CONF.dns_server
1150 if CONF.use_network_dns_servers:
1151 if network_ref.get('dns1'):
1152 dns_servers.append(network_ref.get('dns1'))
1153 if network_ref.get('dns2'):
1154 dns_servers.append(network_ref.get('dns2'))
1155 if network_ref['multi_host']:
1156 cmd.append('--addn-hosts=%s' % _dhcp_file(dev, 'hosts'))
1158 cmd.append('--no-resolv')
1159 for dns_server in dns_servers:
1160 cmd.append('--server=%s' % dns_server)
1162 _execute(*cmd, run_as_root=True)
1164 _add_dnsmasq_accept_rules(dev)
1167 @utils.synchronized('radvd_start')
1168 def update_ra(context, dev, network_ref):
1169 conffile = _ra_file(dev, 'conf')
1174 MinRtrAdvInterval 3;
1175 MaxRtrAdvInterval 10;
1182 """ % (dev, network_ref['cidr_v6'])
1183 write_to_file(conffile, conf_str)
1185 # Make sure radvd can actually read it (it setuid()s to "nobody")
1186 os.chmod(conffile, 0o644)
1188 pid = _ra_pid_for(dev)
1190 # if radvd is already running, then tell it to reload
1192 if is_pid_cmdline_correct(pid, conffile):
1194 _execute('kill', pid, run_as_root=True)
1195 except Exception as exc:
1196 LOG.error(_LE('killing radvd threw %s'), exc)
1198 LOG.debug('Pid %d is stale, relaunching radvd', pid)
1201 '-C', '%s' % _ra_file(dev, 'conf'),
1202 '-p', '%s' % _ra_file(dev, 'pid')]
1204 _execute(*cmd, run_as_root=True)
1207 def _host_lease(fixedip):
1208 """Return a host string for an address in leasefile format."""
1209 timestamp = timeutils.utcnow()
1210 seconds_since_epoch = calendar.timegm(timestamp.utctimetuple())
1211 return '%d %s %s %s *' % (seconds_since_epoch + CONF.dhcp_lease_time,
1212 fixedip.virtual_interface.address,
1214 fixedip.instance.hostname or '*')
1217 def _host_dhcp_network(vif_id):
1218 return 'NW-%s' % vif_id
1221 def _host_dhcp(fixedip):
1222 """Return a host string for an address in dhcp-host format."""
1223 # NOTE(cfb): dnsmasq on linux only supports 64 characters in the hostname
1224 # field (LP #1238910). Since the . counts as a character we need
1225 # to truncate the hostname to only 63 characters.
1226 hostname = fixedip.instance.hostname
1227 if len(hostname) > 63:
1228 LOG.warning(_LW('hostname %s too long, truncating.') % (hostname))
1229 hostname = fixedip.instance.hostname[:2] + '-' +\
1230 fixedip.instance.hostname[-60:]
1231 if CONF.use_single_default_gateway:
1232 net = _host_dhcp_network(fixedip.virtual_interface_id)
1233 return '%s,%s.%s,%s,net:%s' % (fixedip.virtual_interface.address,
1239 return '%s,%s.%s,%s' % (fixedip.virtual_interface.address,
1245 def _host_dns(fixedip):
1246 return '%s\t%s.%s' % (fixedip.address,
1247 fixedip.instance.hostname,
1251 def _host_dhcp_opts(vif_id=None, gateway=None):
1252 """Return an empty gateway option."""
1254 if vif_id is not None:
1255 values.append(_host_dhcp_network(vif_id))
1256 # NOTE(vish): 3 is the dhcp option for gateway.
1259 values.append('%s' % gateway)
1260 return ','.join(values)
1263 def _execute(*cmd, **kwargs):
1264 """Wrapper around utils._execute for fake_network."""
1265 if CONF.fake_network:
1266 LOG.debug('FAKE NET: %s', ' '.join(map(str, cmd)))
1269 return utils.execute(*cmd, **kwargs)
1272 def device_exists(device):
1273 """Check if ethernet device exists."""
1274 return os.path.exists('/sys/class/net/%s' % device)
1277 def _dhcp_file(dev, kind):
1278 """Return path to a pid, leases, hosts or conf file for a bridge/device."""
1279 fileutils.ensure_tree(CONF.networks_path)
1280 return os.path.abspath('%s/nova-%s.%s' % (CONF.networks_path,
1285 def _ra_file(dev, kind):
1286 """Return path to a pid or conf file for a bridge/device."""
1287 fileutils.ensure_tree(CONF.networks_path)
1288 return os.path.abspath('%s/nova-ra-%s.%s' % (CONF.networks_path,
1293 def _dnsmasq_pid_for(dev):
1294 """Returns the pid for prior dnsmasq instance for a bridge/device.
1296 Returns None if no pid file exists.
1298 If machine has rebooted pid might be incorrect (caller should check).
1301 pid_file = _dhcp_file(dev, 'pid')
1303 if os.path.exists(pid_file):
1305 with open(pid_file, 'r') as f:
1306 return int(f.read())
1307 except (ValueError, IOError):
1311 def _ra_pid_for(dev):
1312 """Returns the pid for prior radvd instance for a bridge/device.
1314 Returns None if no pid file exists.
1316 If machine has rebooted pid might be incorrect (caller should check).
1319 pid_file = _ra_file(dev, 'pid')
1321 if os.path.exists(pid_file):
1322 with open(pid_file, 'r') as f:
1323 return int(f.read())
1326 def _ip_bridge_cmd(action, params, device):
1327 """Build commands to add/del ips to bridges/devices."""
1328 cmd = ['ip', 'addr', action]
1330 cmd.extend(['dev', device])
1334 def _set_device_mtu(dev, mtu=None):
1335 """Set the device MTU."""
1338 mtu = CONF.network_device_mtu
1340 utils.execute('ip', 'link', 'set', dev, 'mtu',
1341 mtu, run_as_root=True,
1342 check_exit_code=[0, 1, 2, 254])
1345 def _create_veth_pair(dev1_name, dev2_name):
1346 """Create a pair of veth devices with the specified names,
1347 deleting any previous devices with those names.
1349 for dev in [dev1_name, dev2_name]:
1352 utils.execute('ip', 'link', 'add', dev1_name, 'type', 'veth', 'peer',
1353 'name', dev2_name, run_as_root=True)
1354 for dev in [dev1_name, dev2_name]:
1355 utils.execute('ip', 'link', 'set', dev, 'up', run_as_root=True)
1356 utils.execute('ip', 'link', 'set', dev, 'promisc', 'on',
1358 _set_device_mtu(dev)
1361 def _ovs_vsctl(args):
1362 full_args = ['ovs-vsctl', '--timeout=%s' % CONF.ovs_vsctl_timeout] + args
1364 return utils.execute(*full_args, run_as_root=True)
1365 except Exception as e:
1366 LOG.error(_LE("Unable to execute %(cmd)s. Exception: %(exception)s"),
1367 {'cmd': full_args, 'exception': e})
1368 raise exception.AgentError(method=full_args)
1371 def create_ovs_vif_port(bridge, dev, iface_id, mac, instance_id):
1372 _ovs_vsctl(['--', '--if-exists', 'del-port', dev, '--',
1373 'add-port', bridge, dev,
1374 '--', 'set', 'Interface', dev,
1375 'external-ids:iface-id=%s' % iface_id,
1376 'external-ids:iface-status=active',
1377 'external-ids:attached-mac=%s' % mac,
1378 'external-ids:vm-uuid=%s' % instance_id])
1379 _set_device_mtu(dev)
1382 def delete_ovs_vif_port(bridge, dev):
1383 _ovs_vsctl(['--', '--if-exists', 'del-port', bridge, dev])
1387 def ovs_set_vhostuser_port_type(dev):
1388 _ovs_vsctl(['--', 'set', 'Interface', dev, 'type=dpdkvhostuser'])
1391 def create_ivs_vif_port(dev, iface_id, mac, instance_id):
1392 utils.execute('ivs-ctl', 'add-port',
1393 dev, run_as_root=True)
1396 def delete_ivs_vif_port(dev):
1397 utils.execute('ivs-ctl', 'del-port', dev,
1399 utils.execute('ip', 'link', 'delete', dev,
1403 def create_tap_dev(dev, mac_address=None):
1404 if not device_exists(dev):
1406 # First, try with 'ip'
1407 utils.execute('ip', 'tuntap', 'add', dev, 'mode', 'tap',
1408 run_as_root=True, check_exit_code=[0, 2, 254])
1409 except processutils.ProcessExecutionError:
1410 # Second option: tunctl
1411 utils.execute('tunctl', '-b', '-t', dev, run_as_root=True)
1413 utils.execute('ip', 'link', 'set', dev, 'address', mac_address,
1414 run_as_root=True, check_exit_code=[0, 2, 254])
1415 utils.execute('ip', 'link', 'set', dev, 'up', run_as_root=True,
1416 check_exit_code=[0, 2, 254])
1419 def delete_net_dev(dev):
1420 """Delete a network device only if it exists."""
1421 if device_exists(dev):
1423 utils.execute('ip', 'link', 'delete', dev, run_as_root=True,
1424 check_exit_code=[0, 2, 254])
1425 LOG.debug("Net device removed: '%s'", dev)
1426 except processutils.ProcessExecutionError:
1427 with excutils.save_and_reraise_exception():
1428 LOG.error(_LE("Failed removing net device: '%s'"), dev)
1431 def delete_bridge_dev(dev):
1432 """Delete a network bridge."""
1433 if device_exists(dev):
1435 utils.execute('ip', 'link', 'set', dev, 'down', run_as_root=True)
1436 utils.execute('brctl', 'delbr', dev, run_as_root=True)
1437 except processutils.ProcessExecutionError:
1438 with excutils.save_and_reraise_exception():
1439 LOG.error(_LE("Failed removing bridge device: '%s'"), dev)
1442 # Similar to compute virt layers, the Linux network node
1443 # code uses a flexible driver model to support different ways
1444 # of creating ethernet interfaces and attaching them to the network.
1445 # In the case of a network host, these interfaces
1446 # act as gateway/dhcp/vpn/etc. endpoints not VM interfaces.
1447 interface_driver = None
1450 def _get_interface_driver():
1451 global interface_driver
1452 if not interface_driver:
1453 interface_driver = importutils.import_object(
1454 CONF.linuxnet_interface_driver)
1455 return interface_driver
1458 def plug(network, mac_address, gateway=True):
1459 return _get_interface_driver().plug(network, mac_address, gateway)
1462 def unplug(network):
1463 return _get_interface_driver().unplug(network)
1466 def get_dev(network):
1467 return _get_interface_driver().get_dev(network)
1470 class LinuxNetInterfaceDriver(object):
1471 """Abstract class that defines generic network host API
1472 for all Linux interface drivers.
1475 def plug(self, network, mac_address):
1476 """Create Linux device, return device name."""
1477 raise NotImplementedError()
1479 def unplug(self, network):
1480 """Destroy Linux device, return device name."""
1481 raise NotImplementedError()
1483 def get_dev(self, network):
1484 """Get device name."""
1485 raise NotImplementedError()
1488 # plugs interfaces using Linux Bridge
1489 class LinuxBridgeInterfaceDriver(LinuxNetInterfaceDriver):
1491 def plug(self, network, mac_address, gateway=True):
1492 vlan = network.get('vlan')
1493 if vlan is not None:
1494 iface = CONF.vlan_interface or network['bridge_interface']
1495 LinuxBridgeInterfaceDriver.ensure_vlan_bridge(
1502 iface = 'vlan%s' % vlan
1504 iface = CONF.flat_interface or network['bridge_interface']
1505 LinuxBridgeInterfaceDriver.ensure_bridge(
1510 if network['share_address'] or CONF.share_dhcp_address:
1511 isolate_dhcp_address(iface, network['dhcp_server'])
1512 # NOTE(vish): applying here so we don't get a lock conflict
1513 iptables_manager.apply()
1514 return network['bridge']
1516 def unplug(self, network, gateway=True):
1517 vlan = network.get('vlan')
1518 if vlan is not None:
1519 iface = 'vlan%s' % vlan
1520 LinuxBridgeInterfaceDriver.remove_vlan_bridge(vlan,
1523 iface = CONF.flat_interface or network['bridge_interface']
1524 LinuxBridgeInterfaceDriver.remove_bridge(network['bridge'],
1527 if network['share_address'] or CONF.share_dhcp_address:
1528 remove_isolate_dhcp_address(iface, network['dhcp_server'])
1530 iptables_manager.apply()
1531 return self.get_dev(network)
1533 def get_dev(self, network):
1534 return network['bridge']
1537 def ensure_vlan_bridge(vlan_num, bridge, bridge_interface,
1538 net_attrs=None, mac_address=None,
1540 """Create a vlan and bridge unless they already exist."""
1541 interface = LinuxBridgeInterfaceDriver.ensure_vlan(vlan_num,
1542 bridge_interface, mac_address,
1544 LinuxBridgeInterfaceDriver.ensure_bridge(bridge, interface, net_attrs)
1548 def remove_vlan_bridge(vlan_num, bridge):
1549 """Delete a bridge and vlan."""
1550 LinuxBridgeInterfaceDriver.remove_bridge(bridge)
1551 LinuxBridgeInterfaceDriver.remove_vlan(vlan_num)
1554 @utils.synchronized('lock_vlan', external=True)
1555 def ensure_vlan(vlan_num, bridge_interface, mac_address=None, mtu=None,
1557 """Create a vlan unless it already exists."""
1558 if interface is None:
1559 interface = 'vlan%s' % vlan_num
1560 if not device_exists(interface):
1561 LOG.debug('Starting VLAN interface %s', interface)
1562 _execute('ip', 'link', 'add', 'link', bridge_interface,
1563 'name', interface, 'type', 'vlan',
1564 'id', vlan_num, run_as_root=True,
1565 check_exit_code=[0, 2, 254])
1566 # (danwent) the bridge will inherit this address, so we want to
1567 # make sure it is the value set from the NetworkManager
1569 _execute('ip', 'link', 'set', interface, 'address',
1570 mac_address, run_as_root=True,
1571 check_exit_code=[0, 2, 254])
1572 _execute('ip', 'link', 'set', interface, 'up', run_as_root=True,
1573 check_exit_code=[0, 2, 254])
1574 # NOTE(vish): set mtu every time to ensure that changes to mtu get
1576 _set_device_mtu(interface, mtu)
1580 @utils.synchronized('lock_vlan', external=True)
1581 def remove_vlan(vlan_num):
1582 """Delete a vlan."""
1583 vlan_interface = 'vlan%s' % vlan_num
1584 delete_net_dev(vlan_interface)
1587 @utils.synchronized('lock_bridge', external=True)
1588 def ensure_bridge(bridge, interface, net_attrs=None, gateway=True,
1590 """Create a bridge unless it already exists.
1592 :param interface: the interface to create the bridge on.
1593 :param net_attrs: dictionary with attributes used to create bridge.
1594 :param gateway: whether or not the bridge is a gateway.
1595 :param filtering: whether or not to create filters on the bridge.
1597 If net_attrs is set, it will add the net_attrs['gateway'] to the bridge
1598 using net_attrs['broadcast'] and net_attrs['cidr']. It will also add
1599 the ip_v6 address specified in net_attrs['cidr_v6'] if use_ipv6 is set.
1601 The code will attempt to move any ips that already exist on the
1602 interface onto the bridge and reset the default gateway if necessary.
1605 if not device_exists(bridge):
1606 LOG.debug('Starting Bridge %s', bridge)
1607 out, err = _execute('brctl', 'addbr', bridge,
1608 check_exit_code=False, run_as_root=True)
1609 if (err and err != "device %s already exists; can't create "
1610 "bridge with the same name\n" % (bridge)):
1611 msg = _('Failed to add bridge: %s') % err
1612 raise exception.NovaException(msg)
1614 _execute('brctl', 'setfd', bridge, 0, run_as_root=True)
1615 # _execute('brctl setageing %s 10' % bridge, run_as_root=True)
1616 _execute('brctl', 'stp', bridge, 'off', run_as_root=True)
1617 _execute('ip', 'link', 'set', bridge, 'up', run_as_root=True)
1620 LOG.debug('Adding interface %(interface)s to bridge %(bridge)s',
1621 {'interface': interface, 'bridge': bridge})
1622 out, err = _execute('brctl', 'addif', bridge, interface,
1623 check_exit_code=False, run_as_root=True)
1624 if (err and err != "device %s is already a member of a bridge; "
1625 "can't enslave it to bridge %s.\n" % (interface, bridge)):
1626 msg = _('Failed to add interface: %s') % err
1627 raise exception.NovaException(msg)
1629 # NOTE(apmelton): Linux bridge's default behavior is to use the
1630 # lowest mac of all plugged interfaces. This isn't a problem when
1631 # it is first created and the only interface is the bridged
1632 # interface. But, as instance interfaces are plugged, there is a
1633 # chance for the mac to change. So, set it here so that it won't
1634 # change in the future.
1635 if not CONF.fake_network:
1636 interface_addrs = netifaces.ifaddresses(interface)
1637 interface_mac = interface_addrs[netifaces.AF_LINK][0]['addr']
1638 _execute('ip', 'link', 'set', bridge, 'address', interface_mac,
1641 out, err = _execute('ip', 'link', 'set', interface, 'up',
1642 check_exit_code=False, run_as_root=True)
1644 # NOTE(vish): This will break if there is already an ip on the
1645 # interface, so we move any ips to the bridge
1646 # NOTE(danms): We also need to copy routes to the bridge so as
1647 # not to break existing connectivity on the interface
1649 out, err = _execute('ip', 'route', 'show', 'dev', interface)
1650 for line in out.split('\n'):
1651 fields = line.split()
1652 if fields and 'via' in fields:
1653 old_routes.append(fields)
1654 _execute('ip', 'route', 'del', *fields,
1656 out, err = _execute('ip', 'addr', 'show', 'dev', interface,
1658 for line in out.split('\n'):
1659 fields = line.split()
1660 if fields and fields[0] == 'inet':
1661 if fields[-2] in ('secondary', 'dynamic', ):
1662 params = fields[1:-2]
1664 params = fields[1:-1]
1665 _execute(*_ip_bridge_cmd('del', params, fields[-1]),
1666 run_as_root=True, check_exit_code=[0, 2, 254])
1667 _execute(*_ip_bridge_cmd('add', params, bridge),
1668 run_as_root=True, check_exit_code=[0, 2, 254])
1669 for fields in old_routes:
1670 _execute('ip', 'route', 'add', *fields,
1674 # Don't forward traffic unless we were told to be a gateway
1675 ipv4_filter = iptables_manager.ipv4['filter']
1677 for rule in get_gateway_rules(bridge):
1678 ipv4_filter.add_rule(*rule)
1680 ipv4_filter.add_rule('FORWARD',
1681 ('--in-interface %s -j %s'
1682 % (bridge, CONF.iptables_drop_action)))
1683 ipv4_filter.add_rule('FORWARD',
1684 ('--out-interface %s -j %s'
1685 % (bridge, CONF.iptables_drop_action)))
1688 @utils.synchronized('lock_bridge', external=True)
1689 def remove_bridge(bridge, gateway=True, filtering=True):
1690 """Delete a bridge."""
1691 if not device_exists(bridge):
1695 ipv4_filter = iptables_manager.ipv4['filter']
1697 for rule in get_gateway_rules(bridge):
1698 ipv4_filter.remove_rule(*rule)
1700 drop_actions = ['DROP']
1701 if CONF.iptables_drop_action != 'DROP':
1702 drop_actions.append(CONF.iptables_drop_action)
1704 for drop_action in drop_actions:
1705 ipv4_filter.remove_rule('FORWARD',
1706 ('--in-interface %s -j %s'
1707 % (bridge, drop_action)))
1708 ipv4_filter.remove_rule('FORWARD',
1709 ('--out-interface %s -j %s'
1710 % (bridge, drop_action)))
1711 delete_bridge_dev(bridge)
1714 # NOTE(cfb): This is a temporary fix to LP #1316621. We really want to call
1715 # ebtables with --concurrent. In order to do that though we need
1716 # libvirt to support this. Additionally since ebtables --concurrent
1717 # will hang indefinitely waiting on the lock we need to teach
1718 # oslo_concurrency.processutils how to timeout a long running
1719 # process first. Once those are complete we can replace all of this
1720 # with calls to ebtables --concurrent and a reasonable timeout.
1721 def _exec_ebtables(*cmd, **kwargs):
1722 check_exit_code = kwargs.pop('check_exit_code', True)
1724 # List of error strings to re-try.
1726 'Multiple ebtables programs',
1729 # We always try at least once
1730 attempts = CONF.ebtables_exec_attempts
1734 while count <= attempts:
1735 # Updated our counters if needed
1736 sleep = CONF.ebtables_retry_interval * count
1738 # NOTE(cfb): ebtables reports all errors with a return code of 255.
1739 # As such we can't know if we hit a locking error, or some
1740 # other error (like a rule doesn't exist) so we have to
1743 _execute(*cmd, check_exit_code=[0], **kwargs)
1744 except processutils.ProcessExecutionError as exc:
1745 # See if we can retry the error.
1746 if any(error in exc.stderr for error in retry_strings):
1747 if count > attempts and check_exit_code:
1748 LOG.warning(_LW('%s failed. Not Retrying.'), ' '.join(cmd))
1751 # We need to sleep a bit before retrying
1752 LOG.warning(_LW("%(cmd)s failed. Sleeping %(time)s "
1753 "seconds before retry."),
1754 {'cmd': ' '.join(cmd), 'time': sleep})
1757 # Not eligible for retry
1759 LOG.warning(_LW('%s failed. Not Retrying.'), ' '.join(cmd))
1768 @utils.synchronized('ebtables', external=True)
1769 def ensure_ebtables_rules(rules, table='filter'):
1771 cmd = ['ebtables', '-t', table, '-D'] + rule.split()
1772 _exec_ebtables(*cmd, check_exit_code=False, run_as_root=True)
1774 _exec_ebtables(*cmd, run_as_root=True)
1777 @utils.synchronized('ebtables', external=True)
1778 def remove_ebtables_rules(rules, table='filter'):
1780 cmd = ['ebtables', '-t', table, '-D'] + rule.split()
1781 _exec_ebtables(*cmd, check_exit_code=False, run_as_root=True)
1784 def isolate_dhcp_address(interface, address):
1785 # block arp traffic to address across the interface
1787 rules.append('INPUT -p ARP -i %s --arp-ip-dst %s -j DROP'
1788 % (interface, address))
1789 rules.append('OUTPUT -p ARP -o %s --arp-ip-src %s -j DROP'
1790 % (interface, address))
1791 rules.append('FORWARD -p IPv4 -i %s --ip-protocol udp '
1792 '--ip-destination-port 67:68 -j DROP'
1794 rules.append('FORWARD -p IPv4 -o %s --ip-protocol udp '
1795 '--ip-destination-port 67:68 -j DROP'
1797 # NOTE(vish): the above is not possible with iptables/arptables
1798 ensure_ebtables_rules(rules)
1801 def remove_isolate_dhcp_address(interface, address):
1802 # block arp traffic to address across the interface
1804 rules.append('INPUT -p ARP -i %s --arp-ip-dst %s -j DROP'
1805 % (interface, address))
1806 rules.append('OUTPUT -p ARP -o %s --arp-ip-src %s -j DROP'
1807 % (interface, address))
1808 rules.append('FORWARD -p IPv4 -i %s --ip-protocol udp '
1809 '--ip-destination-port 67:68 -j DROP'
1811 rules.append('FORWARD -p IPv4 -o %s --ip-protocol udp '
1812 '--ip-destination-port 67:68 -j DROP'
1814 remove_ebtables_rules(rules)
1815 # NOTE(vish): the above is not possible with iptables/arptables
1818 def get_gateway_rules(bridge):
1819 interfaces = CONF.forward_bridge_interface
1820 if 'all' in interfaces:
1821 return [('FORWARD', '-i %s -j ACCEPT' % bridge),
1822 ('FORWARD', '-o %s -j ACCEPT' % bridge)]
1824 for iface in CONF.forward_bridge_interface:
1826 rules.append(('FORWARD', '-i %s -o %s -j ACCEPT' % (bridge,
1828 rules.append(('FORWARD', '-i %s -o %s -j ACCEPT' % (iface,
1830 rules.append(('FORWARD', '-i %s -o %s -j ACCEPT' % (bridge, bridge)))
1831 rules.append(('FORWARD', '-i %s -j %s' % (bridge,
1832 CONF.iptables_drop_action)))
1833 rules.append(('FORWARD', '-o %s -j %s' % (bridge,
1834 CONF.iptables_drop_action)))
1838 # plugs interfaces using Open vSwitch
1839 class LinuxOVSInterfaceDriver(LinuxNetInterfaceDriver):
1841 def plug(self, network, mac_address, gateway=True):
1842 dev = self.get_dev(network)
1843 if not device_exists(dev):
1844 bridge = CONF.linuxnet_ovs_integration_bridge
1845 _ovs_vsctl(['--', '--may-exist', 'add-port', bridge, dev,
1846 '--', 'set', 'Interface', dev, 'type=internal',
1847 '--', 'set', 'Interface', dev,
1848 'external-ids:iface-id=%s' % dev,
1849 '--', 'set', 'Interface', dev,
1850 'external-ids:iface-status=active',
1851 '--', 'set', 'Interface', dev,
1852 'external-ids:attached-mac=%s' % mac_address])
1853 _execute('ip', 'link', 'set', dev, 'address', mac_address,
1855 _set_device_mtu(dev, network.get('mtu'))
1856 _execute('ip', 'link', 'set', dev, 'up', run_as_root=True)
1858 # If we weren't instructed to act as a gateway then add the
1859 # appropriate flows to block all non-dhcp traffic.
1860 _execute('ovs-ofctl',
1861 'add-flow', bridge, 'priority=1,actions=drop',
1863 _execute('ovs-ofctl', 'add-flow', bridge,
1864 'udp,tp_dst=67,dl_dst=%s,priority=2,actions=normal' %
1865 mac_address, run_as_root=True)
1866 # .. and make sure iptbles won't forward it as well.
1867 iptables_manager.ipv4['filter'].add_rule('FORWARD',
1868 '--in-interface %s -j %s' % (bridge,
1869 CONF.iptables_drop_action))
1870 iptables_manager.ipv4['filter'].add_rule('FORWARD',
1871 '--out-interface %s -j %s' % (bridge,
1872 CONF.iptables_drop_action))
1874 for rule in get_gateway_rules(bridge):
1875 iptables_manager.ipv4['filter'].add_rule(*rule)
1879 def unplug(self, network):
1880 dev = self.get_dev(network)
1881 bridge = CONF.linuxnet_ovs_integration_bridge
1882 _ovs_vsctl(['--', '--if-exists', 'del-port', bridge, dev])
1885 def get_dev(self, network):
1886 dev = 'gw-' + str(network['uuid'][0:11])
1890 # plugs interfaces using Linux Bridge when using NeutronManager
1891 class NeutronLinuxBridgeInterfaceDriver(LinuxNetInterfaceDriver):
1893 BRIDGE_NAME_PREFIX = 'brq'
1894 GATEWAY_INTERFACE_PREFIX = 'gw-'
1896 def plug(self, network, mac_address, gateway=True):
1897 dev = self.get_dev(network)
1898 bridge = self.get_bridge(network)
1900 # If we weren't instructed to act as a gateway then add the
1901 # appropriate flows to block all non-dhcp traffic.
1902 # .. and make sure iptbles won't forward it as well.
1903 iptables_manager.ipv4['filter'].add_rule('FORWARD',
1904 ('--in-interface %s -j %s'
1905 % (bridge, CONF.iptables_drop_action)))
1906 iptables_manager.ipv4['filter'].add_rule('FORWARD',
1907 ('--out-interface %s -j %s'
1908 % (bridge, CONF.iptables_drop_action)))
1911 for rule in get_gateway_rules(bridge):
1912 iptables_manager.ipv4['filter'].add_rule(*rule)
1914 create_tap_dev(dev, mac_address)
1916 if not device_exists(bridge):
1917 LOG.debug("Starting bridge %s ", bridge)
1918 utils.execute('brctl', 'addbr', bridge, run_as_root=True)
1919 utils.execute('brctl', 'setfd', bridge, str(0), run_as_root=True)
1920 utils.execute('brctl', 'stp', bridge, 'off', run_as_root=True)
1921 utils.execute('ip', 'link', 'set', bridge, 'address', mac_address,
1922 run_as_root=True, check_exit_code=[0, 2, 254])
1923 utils.execute('ip', 'link', 'set', bridge, 'up', run_as_root=True,
1924 check_exit_code=[0, 2, 254])
1925 LOG.debug("Done starting bridge %s", bridge)
1927 full_ip = '%s/%s' % (network['dhcp_server'],
1928 network['cidr'].rpartition('/')[2])
1929 utils.execute('ip', 'address', 'add', full_ip, 'dev', bridge,
1930 run_as_root=True, check_exit_code=[0, 2, 254])
1934 def unplug(self, network):
1935 dev = self.get_dev(network)
1936 if not device_exists(dev):
1942 def get_dev(self, network):
1943 dev = self.GATEWAY_INTERFACE_PREFIX + str(network['uuid'][0:11])
1946 def get_bridge(self, network):
1947 bridge = self.BRIDGE_NAME_PREFIX + str(network['uuid'][0:11])
1950 # provide compatibility with existing configs
1951 QuantumLinuxBridgeInterfaceDriver = NeutronLinuxBridgeInterfaceDriver
1953 iptables_manager = IptablesManager()
1956 def set_vf_interface_vlan(pci_addr, mac_addr, vlan=0):
1957 pf_ifname = pci_utils.get_ifname_by_pci_address(pci_addr,
1959 vf_ifname = pci_utils.get_ifname_by_pci_address(pci_addr)
1960 vf_num = pci_utils.get_vf_num_by_pci_address(pci_addr)
1962 # Set the VF's mac address and vlan
1963 exit_code = [0, 2, 254]
1964 port_state = 'up' if vlan > 0 else 'down'
1965 utils.execute('ip', 'link', 'set', pf_ifname,
1970 check_exit_code=exit_code)
1971 # Bring up/down the VF's interface
1972 utils.execute('ip', 'link', 'set', vf_ifname,
1975 check_exit_code=exit_code)