fuel-plugins: Fuel8 Liberty rebase
[ovsnfv.git] / fuel-plugin-ovsnfv / deployment_scripts / puppet / modules / ovsdpdk / files / linux_net.py
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.
4 # All Rights Reserved.
5 #
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
9 #
10 #         http://www.apache.org/licenses/LICENSE-2.0
11 #
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
16 #    under the License.
17
18 """Implements vlans, bridges, and iptables rules using linux utilities."""
19
20 import calendar
21 import inspect
22 import os
23 import re
24 import time
25
26 import netaddr
27 import netifaces
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
36 import six
37
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
44
45 LOG = logging.getLogger(__name__)
46
47
48 linux_net_opts = [
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',
56                default='eth0',
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',
62                default='$my_ip',
63                help='Public IP of network host'),
64     cfg.IntOpt('dhcp_lease_time',
65                default=86400,
66                help='Lifetime of a DHCP lease in seconds'),
67     cfg.MultiStrOpt('dns_server',
68                     default=[],
69                     help='If set, uses specific DNS server for dnsmasq. Can'
70                          ' be specified multiple times.'),
71     cfg.BoolOpt('use_network_dns_servers',
72                 default=False,
73                 help='If set, uses the dns1 and dns2 from the network ref.'
74                      ' as dns servers.'),
75     cfg.ListOpt('dmz_cidr',
76                default=[],
77                help='A list of dmz ranges that should be accepted'),
78     cfg.MultiStrOpt('force_snat_range',
79                default=[],
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',
84                default='',
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',
90                default='br-int',
91                help='Name of Open vSwitch bridge used with linuxnet'),
92     cfg.BoolOpt('send_arp_for_ha',
93                 default=False,
94                 help='Send gratuitous ARPs for HA setup'),
95     cfg.IntOpt('send_arp_for_ha_count',
96                default=3,
97                help='Send this many gratuitous ARPs for HA setup'),
98     cfg.BoolOpt('use_single_default_gateway',
99                 default=False,
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',
103                     default=['all'],
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',
108                default='$my_ip',
109                help='The IP address for the metadata API server'),
110     cfg.IntOpt('metadata_port',
111                default=8775,
112                min=1,
113                max=65535,
114                help='The port for the metadata API port'),
115     cfg.StrOpt('iptables_top_regex',
116                default='',
117                help='Regular expression to match the iptables rule that '
118                     'should always be on the top.'),
119     cfg.StrOpt('iptables_bottom_regex',
120                default='',
121                help='Regular expression to match the iptables rule that '
122                     'should always be on the bottom.'),
123     cfg.StrOpt('iptables_drop_action',
124                default='DROP',
125                help='The table that iptables to jump to when a packet is '
126                     'to be dropped.'),
127     cfg.IntOpt('ovs_vsctl_timeout',
128                default=120,
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',
132                 default=False,
133                 help='If passed, use fake network devices and addresses'),
134     cfg.IntOpt('ebtables_exec_attempts',
135                default=3,
136                help='Number of times to retry ebtables commands on failure.'),
137     cfg.FloatOpt('ebtables_retry_interval',
138                  default=1.0,
139                  help='Number of seconds to wait between ebtables retries.'),
140     ]
141
142 CONF = cfg.CONF
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')
148
149
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]
157
158 binary_name = get_binary_name()
159
160
161 class IptablesRule(object):
162     """An iptables rule.
163
164     You shouldn't need to use this class directly, it's only used by
165     IptablesManager.
166
167     """
168
169     def __init__(self, chain, rule, wrap=True, top=False):
170         self.chain = chain
171         self.rule = rule
172         self.wrap = wrap
173         self.top = top
174
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))
180
181     def __ne__(self, other):
182         return not self == other
183
184     def __repr__(self):
185         if self.wrap:
186             chain = '%s-%s' % (binary_name, self.chain)
187         else:
188             chain = self.chain
189         # new rules should have a zero [packet: byte] count
190         return '[0:0] -A %s %s' % (chain, self.rule)
191
192
193 class IptablesTable(object):
194     """An iptables table."""
195
196     def __init__(self):
197         self.rules = []
198         self.remove_rules = []
199         self.chains = set()
200         self.unwrapped_chains = set()
201         self.remove_chains = set()
202         self.dirty = True
203
204     def has_chain(self, name, wrap=True):
205         if wrap:
206             return name in self.chains
207         else:
208             return name in self.unwrapped_chains
209
210     def add_chain(self, name, wrap=True):
211         """Adds a named chain to the table.
212
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.
216
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'.
220
221         """
222         if wrap:
223             self.chains.add(name)
224         else:
225             self.unwrapped_chains.add(name)
226         self.dirty = True
227
228     def remove_chain(self, name, wrap=True):
229         """Remove named chain.
230
231         This removal "cascades". All rule in the chain are removed, as are
232         all rules in other chains that jump to it.
233
234         If the chain is not found, this is merely logged.
235
236         """
237         if wrap:
238             chain_set = self.chains
239         else:
240             chain_set = self.unwrapped_chains
241
242         if name not in chain_set:
243             LOG.warning(_LW('Attempted to remove chain %s which does not '
244                             'exist'), name)
245             return
246         self.dirty = True
247
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()
250         if not wrap:
251             self.remove_chains.add(name)
252         chain_set.remove(name)
253         if not wrap:
254             self.remove_rules += filter(lambda r: r.chain == name, self.rules)
255         self.rules = filter(lambda r: r.chain != name, self.rules)
256
257         if wrap:
258             jump_snippet = '-j %s-%s' % (binary_name, name)
259         else:
260             jump_snippet = '-j %s' % (name,)
261
262         if not wrap:
263             self.remove_rules += filter(lambda r: jump_snippet in r.rule,
264                                         self.rules)
265         self.rules = filter(lambda r: jump_snippet not in r.rule, self.rules)
266
267     def add_rule(self, chain, rule, wrap=True, top=False):
268         """Add a rule to the table.
269
270         This is just like what you'd feed to iptables, just without
271         the '-A <chain name>' bit at the start.
272
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.
276
277         """
278         if wrap and chain not in self.chains:
279             raise ValueError(_('Unknown chain: %r') % chain)
280
281         if '$' in rule:
282             rule = ' '.join(map(self._wrap_target_chain, rule.split(' ')))
283
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})
289         else:
290             self.rules.append(IptablesRule(chain, rule, wrap, top))
291             self.dirty = True
292
293     def _wrap_target_chain(self, s):
294         if s.startswith('$'):
295             return '%s-%s' % (binary_name, s[1:])
296         return s
297
298     def remove_rule(self, chain, rule, wrap=True, top=False):
299         """Remove a rule from a chain.
300
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
303         CLI tool.
304
305         """
306         try:
307             self.rules.remove(IptablesRule(chain, rule, wrap, top))
308             if not wrap:
309                 self.remove_rules.append(IptablesRule(chain, rule, wrap, top))
310             self.dirty = True
311         except ValueError:
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})
316
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)
324         if removed > 0:
325             self.dirty = True
326         return removed
327
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]
332         if chained_rules:
333             self.dirty = True
334         for rule in chained_rules:
335             self.rules.remove(rule)
336
337
338 class IptablesManager(object):
339     """Wrapper for iptables.
340
341     See IptablesTable for some usage docs
342
343     A number of chains are set up to begin with.
344
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.
349
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.
354
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.
358
359     """
360
361     def __init__(self, execute=None):
362         if not execute:
363             self.execute = _execute
364         else:
365             self.execute = execute
366
367         self.ipv4 = {'filter': IptablesTable(),
368                      'nat': IptablesTable(),
369                      'mangle': IptablesTable()}
370         self.ipv6 = {'filter': IptablesTable()}
371
372         self.iptables_apply_deferred = False
373
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)
383
384             tables['filter'].add_chain('local')
385             tables['filter'].add_rule('nova-filter-top', '-j $local',
386                                       wrap=False)
387
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']}}
393
394         for ip_version in builtin_chains:
395             if ip_version == 4:
396                 tables = self.ipv4
397             elif ip_version == 6:
398                 tables = self.ipv6
399
400             for table, chains in six.iteritems(builtin_chains[ip_version]):
401                 for chain in chains:
402                     tables[table].add_chain(chain)
403                     tables[table].add_rule(chain, '-j $%s' % (chain,),
404                                            wrap=False)
405
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',
411                                   wrap=False)
412
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',
417                                   wrap=False)
418
419         # And then we add a float-snat chain and jump to first thing in
420         # the snat chain.
421         self.ipv4['nat'].add_chain('float-snat')
422         self.ipv4['nat'].add_rule('snat', '-j $float-snat')
423
424     def defer_apply_on(self):
425         self.iptables_apply_deferred = True
426
427     def defer_apply_off(self):
428         self.iptables_apply_deferred = False
429         self.apply()
430
431     def dirty(self):
432         for table in six.itervalues(self.ipv4):
433             if table.dirty:
434                 return True
435         if CONF.use_ipv6:
436             for table in six.itervalues(self.ipv6):
437                 if table.dirty:
438                     return True
439         return False
440
441     def apply(self):
442         if self.iptables_apply_deferred:
443             return
444         if self.dirty():
445             self._apply()
446         else:
447             LOG.debug("Skipping apply due to lack of new rules")
448
449     @utils.synchronized('iptables', external=True)
450     def _apply(self):
451         """Apply the current in-memory set of iptables rules.
452
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.
456
457         """
458         s = [('iptables', self.ipv4)]
459         if CONF.use_ipv6:
460             s += [('ip6tables', self.ipv6)]
461
462         for cmd, tables in s:
463             all_tables, _err = self.execute('%s-save' % (cmd,), '-c',
464                                                 run_as_root=True,
465                                                 attempts=5)
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)
471                 table.dirty = False
472             self.execute('%s-restore' % (cmd,), '-c', run_as_root=True,
473                          process_input='\n'.join(all_lines),
474                          attempts=5)
475         LOG.debug("IPTablesManager.apply completed with success")
476
477     def _find_table(self, lines, table_name):
478         if len(lines) < 3:
479             # length only <2 when fake iptables
480             return (0, 0)
481         try:
482             start = lines.index('*%s' % table_name) - 1
483         except ValueError:
484             # Couldn't find table_name
485             return (0, 0)
486         end = lines[start:].index('COMMIT') + start + 2
487         return (start, end)
488
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
493         rules = table.rules
494         remove_rules = table.remove_rules
495
496         if not current_lines:
497             fake_table = ['#Generated by nova',
498                           '*' + table_name, 'COMMIT',
499                           '#Completed by nova']
500             current_lines = fake_table
501
502         # Remove any trace of our rules
503         new_filter = filter(lambda line: binary_name not in line,
504                             current_lines)
505
506         top_rules = []
507         bottom_rules = []
508
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(),
514                                     new_filter)
515             top_rules = temp_filter
516
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(),
522                     new_filter)
523             bottom_rules = temp_filter
524
525         seen_chains = False
526         rules_index = 0
527         for rules_index, rule in enumerate(new_filter):
528             if not seen_chains:
529                 if rule.startswith(':'):
530                     seen_chains = True
531             else:
532                 if not rule.startswith(':'):
533                     break
534
535         if not seen_chains:
536             rules_index = 2
537
538         our_rules = top_rules
539         bot_rules = []
540         for rule in rules:
541             rule_str = str(rule)
542             if rule.top:
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.
546
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
550                 # found.
551
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(),
556                                     new_filter)
557
558                 new_filter = filter(lambda s:
559                                     rule_str.strip() not in s.strip(),
560                                     new_filter)
561                 # if no duplicates, use original rule
562                 if dup_filter:
563                     # grab the last entry, if there is one
564                     dup = dup_filter[-1]
565                     rule_str = str(dup)
566                 else:
567                     rule_str = str(rule)
568                 rule_str.strip()
569
570                 our_rules += [rule_str]
571             else:
572                 bot_rules += [rule_str]
573
574         our_rules += bot_rules
575
576         new_filter[rules_index:rules_index] = our_rules
577
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]' %
581                                                (binary_name, name,)
582                                                for name in chains]
583
584         commit_index = new_filter.index('COMMIT')
585         new_filter[commit_index:commit_index] = bottom_rules
586         seen_lines = set()
587
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]
592             line = line.strip()
593             if line in seen_lines:
594                 return False
595             else:
596                 seen_lines.add(line)
597                 return True
598
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]
606                 line = line.strip()
607                 for chain in remove_chains:
608                     if chain == line:
609                         remove_chains.remove(chain)
610                         return False
611             elif line.startswith('['):
612                 # it's a rule
613                 # ignore [packet:byte] counts at beginning of lines
614                 line = line.split(']', 1)[1]
615                 line = line.strip()
616                 for rule in remove_rules:
617                     # ignore [packet:byte] counts at beginning of rules
618                     rule_str = str(rule)
619                     rule_str = rule_str.split(' ', 1)[1]
620                     rule_str = rule_str.strip()
621                     if rule_str == line:
622                         remove_rules.remove(rule)
623                         return False
624
625             # Leave it alone
626             return True
627
628         # We filter duplicates, letting the *last* occurrence take
629         # precedence.  We also filter out anything in the "remove"
630         # lists.
631         new_filter.reverse()
632         new_filter = filter(_weed_out_duplicates, new_filter)
633         new_filter = filter(_weed_out_removes, new_filter)
634         new_filter.reverse()
635
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)
640
641         return new_filter
642
643
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:
648         f.write(data)
649
650
651 def is_pid_cmdline_correct(pid, match):
652     """Ensure that the cmdline for a pid seems sane
653
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.
657     """
658     try:
659         with open('/proc/%d/cmdline' % pid) as f:
660             cmdline = f.read()
661             return match in cmdline
662     except EnvironmentError:
663         return False
664
665
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' %
673                                           (CONF.metadata_host,
674                                            CONF.metadata_port))
675     else:
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' %
680                                            CONF.metadata_port)
681     iptables_manager.apply()
682
683
684 def _iptables_dest(ip):
685     if ((netaddr.IPAddress(ip).version == 4 and ip == '127.0.0.1')
686         or ip == '::1'):
687         return '-m addrtype --dst-type LOCAL'
688     else:
689         return '-d %s' % ip
690
691
692 def metadata_accept():
693     """Create the filter accept rule for metadata."""
694
695     rule = ('-p tcp -m tcp --dport %s %s -j ACCEPT' %
696             (CONF.metadata_port, _iptables_dest(CONF.metadata_host)))
697
698     if netaddr.IPAddress(CONF.metadata_host).version == 4:
699         iptables_manager.ipv4['filter'].add_rule('INPUT', rule)
700     else:
701         iptables_manager.ipv6['filter'].add_rule('INPUT', rule)
702
703     iptables_manager.apply()
704
705
706 def add_snat_rule(ip_range, is_external=False):
707     if CONF.routing_source_ip:
708         if is_external:
709             if CONF.force_snat_range:
710                 snat_range = CONF.force_snat_range
711             else:
712                 snat_range = []
713         else:
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()
722
723
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.
728
729     add_snat_rule(ip_range, is_external)
730
731     rules = []
732     if 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))
737     if rules:
738         ensure_ebtables_rules(rules, 'nat')
739
740     iptables_manager.ipv4['nat'].add_rule('POSTROUTING',
741                                           '-s %s -d %s/32 -j ACCEPT' %
742                                           (ip_range, CONF.metadata_host))
743
744     for dmz in CONF.dmz_cidr:
745         iptables_manager.ipv4['nat'].add_rule('POSTROUTING',
746                                               '-s %s -d %s -j ACCEPT' %
747                                               (ip_range, dmz))
748
749     iptables_manager.ipv4['nat'].add_rule('POSTROUTING',
750                                           '-s %(range)s -d %(range)s '
751                                           '-m conntrack ! --ctstate DNAT '
752                                           '-j ACCEPT' %
753                                           {'range': ip_range})
754     iptables_manager.apply()
755
756
757 def send_arp_for_ip(ip, device, count):
758     out, err = _execute('arping', '-U', ip,
759                         '-A', '-I', device,
760                         '-c', str(count),
761                         run_as_root=True, check_exit_code=False)
762
763     if err:
764         LOG.debug('arping error for ip %s', ip)
765
766
767 def bind_floating_ip(floating_ip, device):
768     """Bind ip to public interface."""
769     _execute('ip', 'addr', 'add', str(floating_ip) + '/32',
770              'dev', device,
771              run_as_root=True, check_exit_code=[0, 2, 254])
772
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)
775
776
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',
780              'dev', device,
781              run_as_root=True, check_exit_code=[0, 2, 254])
782
783
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])
789
790
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',
794                                              '-d %s -p udp '
795                                              '--dport 1194 '
796                                              '-j ACCEPT' % private_ip)
797     iptables_manager.ipv4['nat'].add_rule('PREROUTING',
798                                           '-d %s -p udp '
799                                           '--dport %s -j DNAT --to %s:1194' %
800                                           (public_ip, port, private_ip))
801     iptables_manager.ipv4['nat'].add_rule('OUTPUT',
802                                           '-d %s -p udp '
803                                           '--dport %s -j DNAT --to %s:1194' %
804                                           (public_ip, port, private_ip))
805     iptables_manager.apply()
806
807
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)
813     if num_rules:
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))
821
822
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))
830
831
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')
837
838
839 def floating_forward_rules(floating_ip, fixed_ip, device):
840     rules = []
841     rule = '-s %s -j SNAT --to %s' % (fixed_ip, floating_ip)
842     if device:
843         rules.append(('float-snat', rule + ' -d %s' % fixed_ip))
844         rules.append(('float-snat', rule + ' -o %s' % device))
845     else:
846         rules.append(('float-snat', rule))
847     rules.append(
848             ('PREROUTING', '-d %s -j DNAT --to %s' % (floating_ip, fixed_ip)))
849     rules.append(
850             ('OUTPUT', '-d %s -j DNAT --to %s' % (floating_ip, fixed_ip)))
851     rules.append(('POSTROUTING', '-s %s -m conntrack --ctstate DNAT -j SNAT '
852                   '--to-source %s' %
853                   (fixed_ip, floating_ip)))
854     return rules
855
856
857 def clean_conntrack(fixed_ip):
858     try:
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)
863
864
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)
870
871
872 @utils.synchronized('lock_gateway', external=True)
873 def initialize_gateway_device(dev, network_ref):
874     if not network_ref:
875         return
876
877     _enable_ipv4_forwarding()
878
879     # NOTE(vish): The ip for dnsmasq has to be the first address on the
880     #             bridge for it to respond to requests properly
881     try:
882         prefix = network_ref.cidr.prefixlen
883     except AttributeError:
884         prefix = network_ref['cidr'].rpartition('/')[2]
885
886     full_ip = '%s/%s' % (network_ref['dhcp_server'], prefix)
887     new_ip_params = [[full_ip, 'brd', network_ref['broadcast']]]
888     old_ip_params = []
889     out, err = _execute('ip', 'addr', 'show', 'dev', dev,
890                         'scope', 'global')
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]
896             else:
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:
902         old_routes = []
903         result = _execute('ip', 'route', 'show', 'dev', dev)
904         if result:
905             out, err = result
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])
918
919         for fields in old_routes:
920             _execute('ip', 'route', 'add', *fields,
921                      run_as_root=True)
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)
925     if CONF.use_ipv6:
926         _execute('ip', '-f', 'inet6', 'addr',
927                  'change', network_ref['cidr_v6'],
928                  'dev', dev, run_as_root=True)
929
930
931 def get_dhcp_leases(context, network_ref):
932     """Return a network's hosts config in dnsmasq leasefile format."""
933     hosts = []
934     host = None
935     if network_ref['multi_host']:
936         host = CONF.host
937     for fixedip in objects.FixedIPList.get_by_network(context,
938                                                       network_ref,
939                                                       host=host):
940         # NOTE(cfb): Don't return a lease entry if the IP isn't
941         #            already leased
942         if fixedip.leased:
943             hosts.append(_host_lease(fixedip))
944
945     return '\n'.join(hosts)
946
947
948 def get_dhcp_hosts(context, network_ref, fixedips):
949     """Get network's hosts config in dhcp-host format."""
950     hosts = []
951     macs = set()
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)
958
959
960 def get_dns_hosts(context, network_ref):
961     """Get network's DNS hosts in hosts format."""
962     hosts = []
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)
967
968
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()
979
980
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()
991
992
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()
1001
1002
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()
1009
1010
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']
1020     hosts = []
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))
1027                 else:
1028                     hosts.append(_host_dhcp_opts(vif_id))
1029     else:
1030         hosts.append(_host_dhcp_opts(None, gateway))
1031     return '\n'.join(hosts)
1032
1033
1034 def release_dhcp(dev, address, mac_address):
1035     if device_exists(dev):
1036         try:
1037             utils.execute('dhcp_release', dev, address, mac_address,
1038                           run_as_root=True)
1039         except processutils.ProcessExecutionError:
1040             raise exception.NetworkDhcpReleaseFailed(address=address,
1041                                                      mac_address=mac_address)
1042
1043
1044 def update_dhcp(context, dev, network_ref):
1045     conffile = _dhcp_file(dev, 'conf')
1046     host = None
1047     if network_ref['multi_host']:
1048         host = CONF.host
1049     fixedips = objects.FixedIPList.get_by_network(context,
1050                                                   network_ref,
1051                                                   host=host)
1052     write_to_file(conffile, get_dhcp_hosts(context, network_ref, fixedips))
1053     restart_dhcp(context, dev, network_ref, fixedips)
1054
1055
1056 def update_dns(context, dev, network_ref):
1057     hostsfile = _dhcp_file(dev, 'hosts')
1058     host = None
1059     if network_ref['multi_host']:
1060         host = CONF.host
1061     fixedips = objects.FixedIPList.get_by_network(context,
1062                                                   network_ref,
1063                                                   host=host)
1064     write_to_file(hostsfile, get_dns_hosts(context, network_ref))
1065     restart_dhcp(context, dev, network_ref, fixedips)
1066
1067
1068 def update_dhcp_hostfile_with_text(dev, hosts_text):
1069     conffile = _dhcp_file(dev, 'conf')
1070     write_to_file(conffile, hosts_text)
1071
1072
1073 def kill_dhcp(dev):
1074     pid = _dnsmasq_pid_for(dev)
1075     if pid:
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)
1080         else:
1081             LOG.debug('Pid %d is stale, skip killing dnsmasq', pid)
1082     _remove_dnsmasq_accept_rules(dev)
1083     _remove_dhcp_mangle_rule(dev)
1084
1085
1086 # NOTE(ja): Sending a HUP only reloads the hostfile, so any
1087 #           configuration options (like dchp-range, vlan, ...)
1088 #           aren't reloaded.
1089 @utils.synchronized('dnsmasq_start')
1090 def restart_dhcp(context, dev, network_ref, fixedips):
1091     """(Re)starts a dnsmasq server for a given network.
1092
1093     If a dnsmasq instance is already running then send a HUP
1094     signal causing it to reload, otherwise spawn a new instance.
1095
1096     """
1097     conffile = _dhcp_file(dev, 'conf')
1098
1099     optsfile = _dhcp_file(dev, 'opts')
1100     write_to_file(optsfile, get_dhcp_opts(context, network_ref, fixedips))
1101     os.chmod(optsfile, 0o644)
1102
1103     _add_dhcp_mangle_rule(dev)
1104
1105     # Make sure dnsmasq can actually read it (it setuid()s to "nobody")
1106     os.chmod(conffile, 0o644)
1107
1108     pid = _dnsmasq_pid_for(dev)
1109
1110     # if dnsmasq is already running, then tell it to reload
1111     if pid:
1112         if is_pid_cmdline_correct(pid, conffile.split('/')[-1]):
1113             try:
1114                 _execute('kill', '-HUP', pid, run_as_root=True)
1115                 _add_dnsmasq_accept_rules(dev)
1116                 return
1117             except Exception as exc:
1118                 LOG.error(_LE('kill -HUP dnsmasq threw %s'), exc)
1119         else:
1120             LOG.debug('Pid %d is stale, relaunching dnsmasq', pid)
1121
1122     cmd = ['env',
1123            'CONFIG_FILE=%s' % jsonutils.dumps(CONF.dhcpbridge_flagfile),
1124            'NETWORK_ID=%s' % str(network_ref['id']),
1125            'dnsmasq',
1126            '--strict-order',
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,
1141            '--no-hosts',
1142            '--leasefile-ro']
1143
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)
1148
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'))
1157     if dns_servers:
1158         cmd.append('--no-resolv')
1159     for dns_server in dns_servers:
1160         cmd.append('--server=%s' % dns_server)
1161
1162     _execute(*cmd, run_as_root=True)
1163
1164     _add_dnsmasq_accept_rules(dev)
1165
1166
1167 @utils.synchronized('radvd_start')
1168 def update_ra(context, dev, network_ref):
1169     conffile = _ra_file(dev, 'conf')
1170     conf_str = """
1171 interface %s
1172 {
1173    AdvSendAdvert on;
1174    MinRtrAdvInterval 3;
1175    MaxRtrAdvInterval 10;
1176    prefix %s
1177    {
1178         AdvOnLink on;
1179         AdvAutonomous on;
1180    };
1181 };
1182 """ % (dev, network_ref['cidr_v6'])
1183     write_to_file(conffile, conf_str)
1184
1185     # Make sure radvd can actually read it (it setuid()s to "nobody")
1186     os.chmod(conffile, 0o644)
1187
1188     pid = _ra_pid_for(dev)
1189
1190     # if radvd is already running, then tell it to reload
1191     if pid:
1192         if is_pid_cmdline_correct(pid, conffile):
1193             try:
1194                 _execute('kill', pid, run_as_root=True)
1195             except Exception as exc:
1196                 LOG.error(_LE('killing radvd threw %s'), exc)
1197         else:
1198             LOG.debug('Pid %d is stale, relaunching radvd', pid)
1199
1200     cmd = ['radvd',
1201            '-C', '%s' % _ra_file(dev, 'conf'),
1202            '-p', '%s' % _ra_file(dev, 'pid')]
1203
1204     _execute(*cmd, run_as_root=True)
1205
1206
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,
1213                               fixedip.address,
1214                               fixedip.instance.hostname or '*')
1215
1216
1217 def _host_dhcp_network(vif_id):
1218     return 'NW-%s' % vif_id
1219
1220
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,
1234                                hostname,
1235                                CONF.dhcp_domain,
1236                                fixedip.address,
1237                                net)
1238     else:
1239         return '%s,%s.%s,%s' % (fixedip.virtual_interface.address,
1240                                hostname,
1241                                CONF.dhcp_domain,
1242                                fixedip.address)
1243
1244
1245 def _host_dns(fixedip):
1246     return '%s\t%s.%s' % (fixedip.address,
1247                           fixedip.instance.hostname,
1248                           CONF.dhcp_domain)
1249
1250
1251 def _host_dhcp_opts(vif_id=None, gateway=None):
1252     """Return an empty gateway option."""
1253     values = []
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.
1257     values.append('3')
1258     if gateway:
1259         values.append('%s' % gateway)
1260     return ','.join(values)
1261
1262
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)))
1267         return 'fake', 0
1268     else:
1269         return utils.execute(*cmd, **kwargs)
1270
1271
1272 def device_exists(device):
1273     """Check if ethernet device exists."""
1274     return os.path.exists('/sys/class/net/%s' % device)
1275
1276
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,
1281                                               dev,
1282                                               kind))
1283
1284
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,
1289                                               dev,
1290                                               kind))
1291
1292
1293 def _dnsmasq_pid_for(dev):
1294     """Returns the pid for prior dnsmasq instance for a bridge/device.
1295
1296     Returns None if no pid file exists.
1297
1298     If machine has rebooted pid might be incorrect (caller should check).
1299
1300     """
1301     pid_file = _dhcp_file(dev, 'pid')
1302
1303     if os.path.exists(pid_file):
1304         try:
1305             with open(pid_file, 'r') as f:
1306                 return int(f.read())
1307         except (ValueError, IOError):
1308             return None
1309
1310
1311 def _ra_pid_for(dev):
1312     """Returns the pid for prior radvd instance for a bridge/device.
1313
1314     Returns None if no pid file exists.
1315
1316     If machine has rebooted pid might be incorrect (caller should check).
1317
1318     """
1319     pid_file = _ra_file(dev, 'pid')
1320
1321     if os.path.exists(pid_file):
1322         with open(pid_file, 'r') as f:
1323             return int(f.read())
1324
1325
1326 def _ip_bridge_cmd(action, params, device):
1327     """Build commands to add/del ips to bridges/devices."""
1328     cmd = ['ip', 'addr', action]
1329     cmd.extend(params)
1330     cmd.extend(['dev', device])
1331     return cmd
1332
1333
1334 def _set_device_mtu(dev, mtu=None):
1335     """Set the device MTU."""
1336
1337     if not mtu:
1338         mtu = CONF.network_device_mtu
1339     if mtu:
1340         utils.execute('ip', 'link', 'set', dev, 'mtu',
1341                       mtu, run_as_root=True,
1342                       check_exit_code=[0, 1, 2, 254])
1343
1344
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.
1348     """
1349     for dev in [dev1_name, dev2_name]:
1350         delete_net_dev(dev)
1351
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',
1357                       run_as_root=True)
1358         _set_device_mtu(dev)
1359
1360
1361 def _ovs_vsctl(args):
1362     full_args = ['ovs-vsctl', '--timeout=%s' % CONF.ovs_vsctl_timeout] + args
1363     try:
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)
1369
1370
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)
1380
1381
1382 def delete_ovs_vif_port(bridge, dev):
1383     _ovs_vsctl(['--', '--if-exists', 'del-port', bridge, dev])
1384     delete_net_dev(dev)
1385
1386
1387 def ovs_set_vhostuser_port_type(dev):
1388     _ovs_vsctl(['--', 'set', 'Interface', dev, 'type=dpdkvhostuser'])
1389
1390
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)
1394
1395
1396 def delete_ivs_vif_port(dev):
1397     utils.execute('ivs-ctl', 'del-port', dev,
1398                   run_as_root=True)
1399     utils.execute('ip', 'link', 'delete', dev,
1400                   run_as_root=True)
1401
1402
1403 def create_tap_dev(dev, mac_address=None):
1404     if not device_exists(dev):
1405         try:
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)
1412         if mac_address:
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])
1417
1418
1419 def delete_net_dev(dev):
1420     """Delete a network device only if it exists."""
1421     if device_exists(dev):
1422         try:
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)
1429
1430
1431 def delete_bridge_dev(dev):
1432     """Delete a network bridge."""
1433     if device_exists(dev):
1434         try:
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)
1440
1441
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
1448
1449
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
1456
1457
1458 def plug(network, mac_address, gateway=True):
1459     return _get_interface_driver().plug(network, mac_address, gateway)
1460
1461
1462 def unplug(network):
1463     return _get_interface_driver().unplug(network)
1464
1465
1466 def get_dev(network):
1467     return _get_interface_driver().get_dev(network)
1468
1469
1470 class LinuxNetInterfaceDriver(object):
1471     """Abstract class that defines generic network host API
1472     for all Linux interface drivers.
1473     """
1474
1475     def plug(self, network, mac_address):
1476         """Create Linux device, return device name."""
1477         raise NotImplementedError()
1478
1479     def unplug(self, network):
1480         """Destroy Linux device, return device name."""
1481         raise NotImplementedError()
1482
1483     def get_dev(self, network):
1484         """Get device name."""
1485         raise NotImplementedError()
1486
1487
1488 # plugs interfaces using Linux Bridge
1489 class LinuxBridgeInterfaceDriver(LinuxNetInterfaceDriver):
1490
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(
1496                            vlan,
1497                            network['bridge'],
1498                            iface,
1499                            network,
1500                            mac_address,
1501                            network.get('mtu'))
1502             iface = 'vlan%s' % vlan
1503         else:
1504             iface = CONF.flat_interface or network['bridge_interface']
1505             LinuxBridgeInterfaceDriver.ensure_bridge(
1506                           network['bridge'],
1507                           iface,
1508                           network, gateway)
1509
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']
1515
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,
1521                                                           network['bridge'])
1522         else:
1523             iface = CONF.flat_interface or network['bridge_interface']
1524             LinuxBridgeInterfaceDriver.remove_bridge(network['bridge'],
1525                                                      gateway)
1526
1527         if network['share_address'] or CONF.share_dhcp_address:
1528             remove_isolate_dhcp_address(iface, network['dhcp_server'])
1529
1530         iptables_manager.apply()
1531         return self.get_dev(network)
1532
1533     def get_dev(self, network):
1534         return network['bridge']
1535
1536     @staticmethod
1537     def ensure_vlan_bridge(vlan_num, bridge, bridge_interface,
1538                            net_attrs=None, mac_address=None,
1539                            mtu=None):
1540         """Create a vlan and bridge unless they already exist."""
1541         interface = LinuxBridgeInterfaceDriver.ensure_vlan(vlan_num,
1542                                                bridge_interface, mac_address,
1543                                                mtu)
1544         LinuxBridgeInterfaceDriver.ensure_bridge(bridge, interface, net_attrs)
1545         return interface
1546
1547     @staticmethod
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)
1552
1553     @staticmethod
1554     @utils.synchronized('lock_vlan', external=True)
1555     def ensure_vlan(vlan_num, bridge_interface, mac_address=None, mtu=None,
1556                     interface=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
1568             if mac_address:
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
1575         #             propogated
1576         _set_device_mtu(interface, mtu)
1577         return interface
1578
1579     @staticmethod
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)
1585
1586     @staticmethod
1587     @utils.synchronized('lock_bridge', external=True)
1588     def ensure_bridge(bridge, interface, net_attrs=None, gateway=True,
1589                       filtering=True):
1590         """Create a bridge unless it already exists.
1591
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.
1596
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.
1600
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.
1603
1604         """
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)
1613
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)
1618
1619         if interface:
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)
1628
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,
1639                          run_as_root=True)
1640
1641             out, err = _execute('ip', 'link', 'set', interface, 'up',
1642                                 check_exit_code=False, run_as_root=True)
1643
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
1648             old_routes = []
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,
1655                              run_as_root=True)
1656             out, err = _execute('ip', 'addr', 'show', 'dev', interface,
1657                                 'scope', 'global')
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]
1663                     else:
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,
1671                          run_as_root=True)
1672
1673         if filtering:
1674             # Don't forward traffic unless we were told to be a gateway
1675             ipv4_filter = iptables_manager.ipv4['filter']
1676             if gateway:
1677                 for rule in get_gateway_rules(bridge):
1678                     ipv4_filter.add_rule(*rule)
1679             else:
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)))
1686
1687     @staticmethod
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):
1692             return
1693         else:
1694             if filtering:
1695                 ipv4_filter = iptables_manager.ipv4['filter']
1696                 if gateway:
1697                     for rule in get_gateway_rules(bridge):
1698                         ipv4_filter.remove_rule(*rule)
1699                 else:
1700                     drop_actions = ['DROP']
1701                     if CONF.iptables_drop_action != 'DROP':
1702                         drop_actions.append(CONF.iptables_drop_action)
1703
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)
1712
1713
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)
1723
1724     # List of error strings to re-try.
1725     retry_strings = (
1726         'Multiple ebtables programs',
1727     )
1728
1729     # We always try at least once
1730     attempts = CONF.ebtables_exec_attempts
1731     if attempts <= 0:
1732         attempts = 1
1733     count = 1
1734     while count <= attempts:
1735         # Updated our counters if needed
1736         sleep = CONF.ebtables_retry_interval * count
1737         count += 1
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
1741         #            to parse stderr.
1742         try:
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))
1749                     raise
1750                 else:
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})
1755                     time.sleep(sleep)
1756             else:
1757                 # Not eligible for retry
1758                 if check_exit_code:
1759                     LOG.warning(_LW('%s failed. Not Retrying.'), ' '.join(cmd))
1760                     raise
1761                 else:
1762                     return
1763         else:
1764             # Success
1765             return
1766
1767
1768 @utils.synchronized('ebtables', external=True)
1769 def ensure_ebtables_rules(rules, table='filter'):
1770     for rule in rules:
1771         cmd = ['ebtables', '-t', table, '-D'] + rule.split()
1772         _exec_ebtables(*cmd, check_exit_code=False, run_as_root=True)
1773         cmd[3] = '-I'
1774         _exec_ebtables(*cmd, run_as_root=True)
1775
1776
1777 @utils.synchronized('ebtables', external=True)
1778 def remove_ebtables_rules(rules, table='filter'):
1779     for rule in rules:
1780         cmd = ['ebtables', '-t', table, '-D'] + rule.split()
1781         _exec_ebtables(*cmd, check_exit_code=False, run_as_root=True)
1782
1783
1784 def isolate_dhcp_address(interface, address):
1785     # block arp traffic to address across the interface
1786     rules = []
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'
1793                  % interface)
1794     rules.append('FORWARD -p IPv4 -o %s --ip-protocol udp '
1795                  '--ip-destination-port 67:68 -j DROP'
1796                  % interface)
1797     # NOTE(vish): the above is not possible with iptables/arptables
1798     ensure_ebtables_rules(rules)
1799
1800
1801 def remove_isolate_dhcp_address(interface, address):
1802     # block arp traffic to address across the interface
1803     rules = []
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'
1810                  % interface)
1811     rules.append('FORWARD -p IPv4 -o %s --ip-protocol udp '
1812                  '--ip-destination-port 67:68 -j DROP'
1813                  % interface)
1814     remove_ebtables_rules(rules)
1815     # NOTE(vish): the above is not possible with iptables/arptables
1816
1817
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)]
1823     rules = []
1824     for iface in CONF.forward_bridge_interface:
1825         if iface:
1826             rules.append(('FORWARD', '-i %s -o %s -j ACCEPT' % (bridge,
1827                                                                 iface)))
1828             rules.append(('FORWARD', '-i %s -o %s -j ACCEPT' % (iface,
1829                                                                 bridge)))
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)))
1835     return rules
1836
1837
1838 # plugs interfaces using Open vSwitch
1839 class LinuxOVSInterfaceDriver(LinuxNetInterfaceDriver):
1840
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,
1854                      run_as_root=True)
1855             _set_device_mtu(dev, network.get('mtu'))
1856             _execute('ip', 'link', 'set', dev, 'up', run_as_root=True)
1857             if not gateway:
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',
1862                          run_as_root=True)
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))
1873             else:
1874                 for rule in get_gateway_rules(bridge):
1875                     iptables_manager.ipv4['filter'].add_rule(*rule)
1876
1877         return dev
1878
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])
1883         return dev
1884
1885     def get_dev(self, network):
1886         dev = 'gw-' + str(network['uuid'][0:11])
1887         return dev
1888
1889
1890 # plugs interfaces using Linux Bridge when using NeutronManager
1891 class NeutronLinuxBridgeInterfaceDriver(LinuxNetInterfaceDriver):
1892
1893     BRIDGE_NAME_PREFIX = 'brq'
1894     GATEWAY_INTERFACE_PREFIX = 'gw-'
1895
1896     def plug(self, network, mac_address, gateway=True):
1897         dev = self.get_dev(network)
1898         bridge = self.get_bridge(network)
1899         if not gateway:
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)))
1909             return bridge
1910         else:
1911             for rule in get_gateway_rules(bridge):
1912                 iptables_manager.ipv4['filter'].add_rule(*rule)
1913
1914         create_tap_dev(dev, mac_address)
1915
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)
1926
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])
1931
1932         return dev
1933
1934     def unplug(self, network):
1935         dev = self.get_dev(network)
1936         if not device_exists(dev):
1937             return None
1938         else:
1939             delete_net_dev(dev)
1940             return dev
1941
1942     def get_dev(self, network):
1943         dev = self.GATEWAY_INTERFACE_PREFIX + str(network['uuid'][0:11])
1944         return dev
1945
1946     def get_bridge(self, network):
1947         bridge = self.BRIDGE_NAME_PREFIX + str(network['uuid'][0:11])
1948         return bridge
1949
1950 # provide compatibility with existing configs
1951 QuantumLinuxBridgeInterfaceDriver = NeutronLinuxBridgeInterfaceDriver
1952
1953 iptables_manager = IptablesManager()
1954
1955
1956 def set_vf_interface_vlan(pci_addr, mac_addr, vlan=0):
1957     pf_ifname = pci_utils.get_ifname_by_pci_address(pci_addr,
1958                                                     pf_interface=True)
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)
1961
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,
1966                   'vf', vf_num,
1967                   'mac', mac_addr,
1968                   'vlan', vlan,
1969                   run_as_root=True,
1970                   check_exit_code=exit_code)
1971     # Bring up/down the VF's interface
1972     utils.execute('ip', 'link', 'set', vf_ifname,
1973                   port_state,
1974                   run_as_root=True,
1975                   check_exit_code=exit_code)