~ubuntu-branches/ubuntu/oneiric/nova/oneiric-updates

« back to all changes in this revision

Viewing changes to .pc/fqdn-in-local-hostname-of-ec2-metadata.patch/nova/network/linux_net.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short, Adam Gandelman, Chuck Short, Scott Moser
  • Date: 2011-09-27 14:56:59 UTC
  • Revision ID: package-import@ubuntu.com-20110927145659-rz5u0ldy09hvwzlg
Tags: 2011.3-0ubuntu3
[Adam Gandelman]
* debian/nova-common.postinst: Create 'nova' group, add user to it
  (LP: #856530)
* debian/nova.conf, debian/nova-compute.upstart.in: Move reference of
  nova-compute.conf from nova.conf to nova-compute's argv. (LP: #839796)

[Chuck Short]
* debian/patches/backport-recreate-gateway-using-dhcp.patch:
  Makes sure to recreate gateway for moved ip. (LP: #859587)
* debian/control: Update Vcs info.

[ Scott Moser ]
* debian/patches/fqdn-in-local-hostname-of-ec2-metadata.patch
  Make the 'local-hostname' in the EC2 Metadata service contain
  the domainname also. (LP: #854614)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
 
2
 
 
3
# Copyright 2010 United States Government as represented by the
 
4
# Administrator of the National Aeronautics and Space Administration.
 
5
# All Rights Reserved.
 
6
#
 
7
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 
8
#    not use this file except in compliance with the License. You may obtain
 
9
#    a copy of the License at
 
10
#
 
11
#         http://www.apache.org/licenses/LICENSE-2.0
 
12
#
 
13
#    Unless required by applicable law or agreed to in writing, software
 
14
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 
15
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 
16
#    License for the specific language governing permissions and limitations
 
17
#    under the License.
 
18
 
 
19
"""Implements vlans, bridges, and iptables rules using linux utilities."""
 
20
 
 
21
import calendar
 
22
import inspect
 
23
import netaddr
 
24
import os
 
25
 
 
26
from nova import db
 
27
from nova import exception
 
28
from nova import flags
 
29
from nova import log as logging
 
30
from nova import utils
 
31
 
 
32
 
 
33
LOG = logging.getLogger("nova.linux_net")
 
34
 
 
35
 
 
36
def _bin_file(script):
 
37
    """Return the absolute path to scipt in the bin directory."""
 
38
    return os.path.abspath(os.path.join(__file__, '../../../bin', script))
 
39
 
 
40
 
 
41
FLAGS = flags.FLAGS
 
42
flags.DEFINE_string('dhcpbridge_flagfile',
 
43
                    '/etc/nova/nova-dhcpbridge.conf',
 
44
                    'location of flagfile for dhcpbridge')
 
45
flags.DEFINE_string('dhcp_domain',
 
46
                    'novalocal',
 
47
                    'domain to use for building the hostnames')
 
48
flags.DEFINE_string('networks_path', '$state_path/networks',
 
49
                    'Location to keep network config files')
 
50
flags.DEFINE_string('public_interface', 'eth0',
 
51
                    'Interface for public IP addresses')
 
52
flags.DEFINE_string('dhcpbridge', _bin_file('nova-dhcpbridge'),
 
53
                        'location of nova-dhcpbridge')
 
54
flags.DEFINE_string('routing_source_ip', '$my_ip',
 
55
                    'Public IP of network host')
 
56
flags.DEFINE_string('input_chain', 'INPUT',
 
57
                    'chain to add nova_input to')
 
58
flags.DEFINE_integer('dhcp_lease_time', 120,
 
59
                     'Lifetime of a DHCP lease')
 
60
flags.DEFINE_string('dns_server', None,
 
61
                    'if set, uses specific dns server for dnsmasq')
 
62
flags.DEFINE_string('dmz_cidr', '10.128.0.0/24',
 
63
                    'dmz range that should be accepted')
 
64
flags.DEFINE_string('dnsmasq_config_file', "",
 
65
                    'Override the default dnsmasq settings with this file')
 
66
flags.DEFINE_string('linuxnet_interface_driver',
 
67
                    'nova.network.linux_net.LinuxBridgeInterfaceDriver',
 
68
                    'Driver used to create ethernet devices.')
 
69
flags.DEFINE_string('linuxnet_ovs_integration_bridge',
 
70
                    'br-int', 'Name of Open vSwitch bridge used with linuxnet')
 
71
flags.DEFINE_bool('send_arp_for_ha', False,
 
72
                  'send gratuitous ARPs for HA setup')
 
73
flags.DEFINE_bool('use_single_default_gateway',
 
74
                   False, 'Use single default gateway. Only first nic of vm'
 
75
                          ' will get default gateway from dhcp server')
 
76
binary_name = os.path.basename(inspect.stack()[-1][1])
 
77
 
 
78
 
 
79
class IptablesRule(object):
 
80
    """An iptables rule.
 
81
 
 
82
    You shouldn't need to use this class directly, it's only used by
 
83
    IptablesManager.
 
84
 
 
85
    """
 
86
 
 
87
    def __init__(self, chain, rule, wrap=True, top=False):
 
88
        self.chain = chain
 
89
        self.rule = rule
 
90
        self.wrap = wrap
 
91
        self.top = top
 
92
 
 
93
    def __eq__(self, other):
 
94
        return ((self.chain == other.chain) and
 
95
                (self.rule == other.rule) and
 
96
                (self.top == other.top) and
 
97
                (self.wrap == other.wrap))
 
98
 
 
99
    def __ne__(self, other):
 
100
        return not self == other
 
101
 
 
102
    def __str__(self):
 
103
        if self.wrap:
 
104
            chain = '%s-%s' % (binary_name, self.chain)
 
105
        else:
 
106
            chain = self.chain
 
107
        return '-A %s %s' % (chain, self.rule)
 
108
 
 
109
 
 
110
class IptablesTable(object):
 
111
    """An iptables table."""
 
112
 
 
113
    def __init__(self):
 
114
        self.rules = []
 
115
        self.chains = set()
 
116
        self.unwrapped_chains = set()
 
117
 
 
118
    def add_chain(self, name, wrap=True):
 
119
        """Adds a named chain to the table.
 
120
 
 
121
        The chain name is wrapped to be unique for the component creating
 
122
        it, so different components of Nova can safely create identically
 
123
        named chains without interfering with one another.
 
124
 
 
125
        At the moment, its wrapped name is <binary name>-<chain name>,
 
126
        so if nova-compute creates a chain named 'OUTPUT', it'll actually
 
127
        end up named 'nova-compute-OUTPUT'.
 
128
 
 
129
        """
 
130
        if wrap:
 
131
            self.chains.add(name)
 
132
        else:
 
133
            self.unwrapped_chains.add(name)
 
134
 
 
135
    def remove_chain(self, name, wrap=True):
 
136
        """Remove named chain.
 
137
 
 
138
        This removal "cascades". All rule in the chain are removed, as are
 
139
        all rules in other chains that jump to it.
 
140
 
 
141
        If the chain is not found, this is merely logged.
 
142
 
 
143
        """
 
144
        if wrap:
 
145
            chain_set = self.chains
 
146
        else:
 
147
            chain_set = self.unwrapped_chains
 
148
 
 
149
        if name not in chain_set:
 
150
            LOG.debug(_('Attempted to remove chain %s which does not exist'),
 
151
                      name)
 
152
            return
 
153
 
 
154
        chain_set.remove(name)
 
155
        self.rules = filter(lambda r: r.chain != name, self.rules)
 
156
 
 
157
        if wrap:
 
158
            jump_snippet = '-j %s-%s' % (binary_name, name)
 
159
        else:
 
160
            jump_snippet = '-j %s' % (name,)
 
161
 
 
162
        self.rules = filter(lambda r: jump_snippet not in r.rule, self.rules)
 
163
 
 
164
    def add_rule(self, chain, rule, wrap=True, top=False):
 
165
        """Add a rule to the table.
 
166
 
 
167
        This is just like what you'd feed to iptables, just without
 
168
        the '-A <chain name>' bit at the start.
 
169
 
 
170
        However, if you need to jump to one of your wrapped chains,
 
171
        prepend its name with a '$' which will ensure the wrapping
 
172
        is applied correctly.
 
173
 
 
174
        """
 
175
        if wrap and chain not in self.chains:
 
176
            raise ValueError(_('Unknown chain: %r') % chain)
 
177
 
 
178
        if '$' in rule:
 
179
            rule = ' '.join(map(self._wrap_target_chain, rule.split(' ')))
 
180
 
 
181
        self.rules.append(IptablesRule(chain, rule, wrap, top))
 
182
 
 
183
    def _wrap_target_chain(self, s):
 
184
        if s.startswith('$'):
 
185
            return '%s-%s' % (binary_name, s[1:])
 
186
        return s
 
187
 
 
188
    def remove_rule(self, chain, rule, wrap=True, top=False):
 
189
        """Remove a rule from a chain.
 
190
 
 
191
        Note: The rule must be exactly identical to the one that was added.
 
192
        You cannot switch arguments around like you can with the iptables
 
193
        CLI tool.
 
194
 
 
195
        """
 
196
        try:
 
197
            self.rules.remove(IptablesRule(chain, rule, wrap, top))
 
198
        except ValueError:
 
199
            LOG.debug(_('Tried to remove rule that was not there:'
 
200
                        ' %(chain)r %(rule)r %(wrap)r %(top)r'),
 
201
                      {'chain': chain, 'rule': rule,
 
202
                       'top': top, 'wrap': wrap})
 
203
 
 
204
    def empty_chain(self, chain, wrap=True):
 
205
        """Remove all rules from a chain."""
 
206
        chained_rules = [rule for rule in self.rules
 
207
                              if rule.chain == chain and rule.wrap == wrap]
 
208
        for rule in chained_rules:
 
209
            self.rules.remove(rule)
 
210
 
 
211
 
 
212
class IptablesManager(object):
 
213
    """Wrapper for iptables.
 
214
 
 
215
    See IptablesTable for some usage docs
 
216
 
 
217
    A number of chains are set up to begin with.
 
218
 
 
219
    First, nova-filter-top. It's added at the top of FORWARD and OUTPUT. Its
 
220
    name is not wrapped, so it's shared between the various nova workers. It's
 
221
    intended for rules that need to live at the top of the FORWARD and OUTPUT
 
222
    chains. It's in both the ipv4 and ipv6 set of tables.
 
223
 
 
224
    For ipv4 and ipv6, the builtin INPUT, OUTPUT, and FORWARD filter chains are
 
225
    wrapped, meaning that the "real" INPUT chain has a rule that jumps to the
 
226
    wrapped INPUT chain, etc. Additionally, there's a wrapped chain named
 
227
    "local" which is jumped to from nova-filter-top.
 
228
 
 
229
    For ipv4, the builtin PREROUTING, OUTPUT, and POSTROUTING nat chains are
 
230
    wrapped in the same was as the builtin filter chains. Additionally, there's
 
231
    a snat chain that is applied after the POSTROUTING chain.
 
232
 
 
233
    """
 
234
 
 
235
    def __init__(self, execute=None):
 
236
        if not execute:
 
237
            self.execute = _execute
 
238
        else:
 
239
            self.execute = execute
 
240
 
 
241
        self.ipv4 = {'filter': IptablesTable(),
 
242
                     'nat': IptablesTable()}
 
243
        self.ipv6 = {'filter': IptablesTable()}
 
244
 
 
245
        # Add a nova-filter-top chain. It's intended to be shared
 
246
        # among the various nova components. It sits at the very top
 
247
        # of FORWARD and OUTPUT.
 
248
        for tables in [self.ipv4, self.ipv6]:
 
249
            tables['filter'].add_chain('nova-filter-top', wrap=False)
 
250
            tables['filter'].add_rule('FORWARD', '-j nova-filter-top',
 
251
                                      wrap=False, top=True)
 
252
            tables['filter'].add_rule('OUTPUT', '-j nova-filter-top',
 
253
                                      wrap=False, top=True)
 
254
 
 
255
            tables['filter'].add_chain('local')
 
256
            tables['filter'].add_rule('nova-filter-top', '-j $local',
 
257
                                      wrap=False)
 
258
 
 
259
        # Wrap the builtin chains
 
260
        builtin_chains = {4: {'filter': ['INPUT', 'OUTPUT', 'FORWARD'],
 
261
                              'nat': ['PREROUTING', 'OUTPUT', 'POSTROUTING']},
 
262
                          6: {'filter': ['INPUT', 'OUTPUT', 'FORWARD']}}
 
263
 
 
264
        for ip_version in builtin_chains:
 
265
            if ip_version == 4:
 
266
                tables = self.ipv4
 
267
            elif ip_version == 6:
 
268
                tables = self.ipv6
 
269
 
 
270
            for table, chains in builtin_chains[ip_version].iteritems():
 
271
                for chain in chains:
 
272
                    tables[table].add_chain(chain)
 
273
                    tables[table].add_rule(chain, '-j $%s' % (chain,),
 
274
                                           wrap=False)
 
275
 
 
276
        # Add a nova-postrouting-bottom chain. It's intended to be shared
 
277
        # among the various nova components. We set it as the last chain
 
278
        # of POSTROUTING chain.
 
279
        self.ipv4['nat'].add_chain('nova-postrouting-bottom', wrap=False)
 
280
        self.ipv4['nat'].add_rule('POSTROUTING', '-j nova-postrouting-bottom',
 
281
                                  wrap=False)
 
282
 
 
283
        # We add a snat chain to the shared nova-postrouting-bottom chain
 
284
        # so that it's applied last.
 
285
        self.ipv4['nat'].add_chain('snat')
 
286
        self.ipv4['nat'].add_rule('nova-postrouting-bottom', '-j $snat',
 
287
                                  wrap=False)
 
288
 
 
289
        # And then we add a floating-snat chain and jump to first thing in
 
290
        # the snat chain.
 
291
        self.ipv4['nat'].add_chain('floating-snat')
 
292
        self.ipv4['nat'].add_rule('snat', '-j $floating-snat')
 
293
 
 
294
    @utils.synchronized('iptables', external=True)
 
295
    def apply(self):
 
296
        """Apply the current in-memory set of iptables rules.
 
297
 
 
298
        This will blow away any rules left over from previous runs of the
 
299
        same component of Nova, and replace them with our current set of
 
300
        rules. This happens atomically, thanks to iptables-restore.
 
301
 
 
302
        """
 
303
        s = [('iptables', self.ipv4)]
 
304
        if FLAGS.use_ipv6:
 
305
            s += [('ip6tables', self.ipv6)]
 
306
 
 
307
        for cmd, tables in s:
 
308
            for table in tables:
 
309
                current_table, _ = self.execute('%s-save' % (cmd,),
 
310
                                                '-t', '%s' % (table,),
 
311
                                                run_as_root=True,
 
312
                                                attempts=5)
 
313
                current_lines = current_table.split('\n')
 
314
                new_filter = self._modify_rules(current_lines,
 
315
                                                tables[table])
 
316
                self.execute('%s-restore' % (cmd,), run_as_root=True,
 
317
                             process_input='\n'.join(new_filter),
 
318
                             attempts=5)
 
319
 
 
320
    def _modify_rules(self, current_lines, table, binary=None):
 
321
        unwrapped_chains = table.unwrapped_chains
 
322
        chains = table.chains
 
323
        rules = table.rules
 
324
 
 
325
        # Remove any trace of our rules
 
326
        new_filter = filter(lambda line: binary_name not in line,
 
327
                            current_lines)
 
328
 
 
329
        seen_chains = False
 
330
        rules_index = 0
 
331
        for rules_index, rule in enumerate(new_filter):
 
332
            if not seen_chains:
 
333
                if rule.startswith(':'):
 
334
                    seen_chains = True
 
335
            else:
 
336
                if not rule.startswith(':'):
 
337
                    break
 
338
 
 
339
        our_rules = []
 
340
        for rule in rules:
 
341
            rule_str = str(rule)
 
342
            if rule.top:
 
343
                # rule.top == True means we want this rule to be at the top.
 
344
                # Further down, we weed out duplicates from the bottom of the
 
345
                # list, so here we remove the dupes ahead of time.
 
346
                new_filter = filter(lambda s: s.strip() != rule_str.strip(),
 
347
                                    new_filter)
 
348
            our_rules += [rule_str]
 
349
 
 
350
        new_filter[rules_index:rules_index] = our_rules
 
351
 
 
352
        new_filter[rules_index:rules_index] = [':%s - [0:0]' % \
 
353
                                               (name,) \
 
354
                                               for name in unwrapped_chains]
 
355
        new_filter[rules_index:rules_index] = [':%s-%s - [0:0]' % \
 
356
                                               (binary_name, name,) \
 
357
                                               for name in chains]
 
358
 
 
359
        seen_lines = set()
 
360
 
 
361
        def _weed_out_duplicates(line):
 
362
            line = line.strip()
 
363
            if line in seen_lines:
 
364
                return False
 
365
            else:
 
366
                seen_lines.add(line)
 
367
                return True
 
368
 
 
369
        # We filter duplicates, letting the *last* occurrence take
 
370
        # precendence.
 
371
        new_filter.reverse()
 
372
        new_filter = filter(_weed_out_duplicates, new_filter)
 
373
        new_filter.reverse()
 
374
        return new_filter
 
375
 
 
376
 
 
377
def metadata_forward():
 
378
    """Create forwarding rule for metadata."""
 
379
    iptables_manager.ipv4['nat'].add_rule('PREROUTING',
 
380
                                          '-s 0.0.0.0/0 -d 169.254.169.254/32 '
 
381
                                          '-p tcp -m tcp --dport 80 -j DNAT '
 
382
                                          '--to-destination %s:%s' % \
 
383
                                          (FLAGS.ec2_dmz_host, FLAGS.ec2_port))
 
384
    iptables_manager.apply()
 
385
 
 
386
 
 
387
def init_host():
 
388
    """Basic networking setup goes here."""
 
389
    # NOTE(devcamcar): Cloud public SNAT entries and the default
 
390
    # SNAT rule for outbound traffic.
 
391
    iptables_manager.ipv4['nat'].add_rule('snat',
 
392
                                          '-s %s -j SNAT --to-source %s' % \
 
393
                                           (FLAGS.fixed_range,
 
394
                                            FLAGS.routing_source_ip))
 
395
 
 
396
    iptables_manager.ipv4['nat'].add_rule('POSTROUTING',
 
397
                                          '-s %s -d %s -j ACCEPT' % \
 
398
                                          (FLAGS.fixed_range, FLAGS.dmz_cidr))
 
399
 
 
400
    iptables_manager.ipv4['nat'].add_rule('POSTROUTING',
 
401
                                          '-s %(range)s -d %(range)s '
 
402
                                          '-j ACCEPT' % \
 
403
                                          {'range': FLAGS.fixed_range})
 
404
    iptables_manager.apply()
 
405
 
 
406
 
 
407
def bind_floating_ip(floating_ip, check_exit_code=True):
 
408
    """Bind ip to public interface."""
 
409
    _execute('ip', 'addr', 'add', floating_ip,
 
410
             'dev', FLAGS.public_interface,
 
411
             run_as_root=True, check_exit_code=check_exit_code)
 
412
    if FLAGS.send_arp_for_ha:
 
413
        _execute('arping', '-U', floating_ip,
 
414
                 '-A', '-I', FLAGS.public_interface,
 
415
                 '-c', 1, run_as_root=True, check_exit_code=False)
 
416
 
 
417
 
 
418
def unbind_floating_ip(floating_ip):
 
419
    """Unbind a public ip from public interface."""
 
420
    _execute('ip', 'addr', 'del', floating_ip,
 
421
             'dev', FLAGS.public_interface, run_as_root=True)
 
422
 
 
423
 
 
424
def ensure_metadata_ip():
 
425
    """Sets up local metadata ip."""
 
426
    _execute('ip', 'addr', 'add', '169.254.169.254/32',
 
427
             'scope', 'link', 'dev', 'lo',
 
428
             run_as_root=True, check_exit_code=False)
 
429
 
 
430
 
 
431
def ensure_vpn_forward(public_ip, port, private_ip):
 
432
    """Sets up forwarding rules for vlan."""
 
433
    iptables_manager.ipv4['filter'].add_rule('FORWARD',
 
434
                                             '-d %s -p udp '
 
435
                                             '--dport 1194 '
 
436
                                             '-j ACCEPT' % private_ip)
 
437
    iptables_manager.ipv4['nat'].add_rule('PREROUTING',
 
438
                                          '-d %s -p udp '
 
439
                                          '--dport %s -j DNAT --to %s:1194' %
 
440
                                          (public_ip, port, private_ip))
 
441
    iptables_manager.ipv4['nat'].add_rule("OUTPUT",
 
442
                                          "-d %s -p udp "
 
443
                                          "--dport %s -j DNAT --to %s:1194" %
 
444
                                          (public_ip, port, private_ip))
 
445
    iptables_manager.apply()
 
446
 
 
447
 
 
448
def ensure_floating_forward(floating_ip, fixed_ip):
 
449
    """Ensure floating ip forwarding rule."""
 
450
    for chain, rule in floating_forward_rules(floating_ip, fixed_ip):
 
451
        iptables_manager.ipv4['nat'].add_rule(chain, rule)
 
452
    iptables_manager.apply()
 
453
 
 
454
 
 
455
def remove_floating_forward(floating_ip, fixed_ip):
 
456
    """Remove forwarding for floating ip."""
 
457
    for chain, rule in floating_forward_rules(floating_ip, fixed_ip):
 
458
        iptables_manager.ipv4['nat'].remove_rule(chain, rule)
 
459
    iptables_manager.apply()
 
460
 
 
461
 
 
462
def floating_forward_rules(floating_ip, fixed_ip):
 
463
    return [('PREROUTING', '-d %s -j DNAT --to %s' % (floating_ip, fixed_ip)),
 
464
            ('OUTPUT', '-d %s -j DNAT --to %s' % (floating_ip, fixed_ip)),
 
465
            ('floating-snat',
 
466
             '-s %s -j SNAT --to %s' % (fixed_ip, floating_ip))]
 
467
 
 
468
 
 
469
def initialize_gateway_device(dev, network_ref):
 
470
    if not network_ref:
 
471
        return
 
472
 
 
473
    # NOTE(vish): The ip for dnsmasq has to be the first address on the
 
474
    #             bridge for it to respond to reqests properly
 
475
    full_ip = '%s/%s' % (network_ref['dhcp_server'],
 
476
                         network_ref['cidr'].rpartition('/')[2])
 
477
    new_ip_params = [[full_ip, 'brd', network_ref['broadcast']]]
 
478
    old_ip_params = []
 
479
    out, err = _execute('ip', 'addr', 'show', 'dev', dev,
 
480
                        'scope', 'global', run_as_root=True)
 
481
    for line in out.split('\n'):
 
482
        fields = line.split()
 
483
        if fields and fields[0] == 'inet':
 
484
            ip_params = fields[1:-1]
 
485
            old_ip_params.append(ip_params)
 
486
            if ip_params[0] != full_ip:
 
487
                new_ip_params.append(ip_params)
 
488
    if not old_ip_params or old_ip_params[0][0] != full_ip:
 
489
        for ip_params in old_ip_params:
 
490
            _execute(*_ip_bridge_cmd('del', ip_params, dev),
 
491
                        run_as_root=True)
 
492
        for ip_params in new_ip_params:
 
493
            _execute(*_ip_bridge_cmd('add', ip_params, dev),
 
494
                        run_as_root=True)
 
495
        if FLAGS.send_arp_for_ha:
 
496
            _execute('arping', '-U', network_ref['dhcp_server'],
 
497
                      '-A', '-I', dev,
 
498
                      '-c', 1, run_as_root=True, check_exit_code=False)
 
499
    if(FLAGS.use_ipv6):
 
500
        _execute('ip', '-f', 'inet6', 'addr',
 
501
                     'change', network_ref['cidr_v6'],
 
502
                     'dev', dev, run_as_root=True)
 
503
    # NOTE(vish): If the public interface is the same as the
 
504
    #             bridge, then the bridge has to be in promiscuous
 
505
    #             to forward packets properly.
 
506
    if(FLAGS.public_interface == dev):
 
507
        _execute('ip', 'link', 'set',
 
508
                     'dev', dev, 'promisc', 'on', run_as_root=True)
 
509
 
 
510
 
 
511
def get_dhcp_leases(context, network_ref):
 
512
    """Return a network's hosts config in dnsmasq leasefile format."""
 
513
    hosts = []
 
514
    for fixed_ref in db.network_get_associated_fixed_ips(context,
 
515
                                                         network_ref['id']):
 
516
        host = fixed_ref['instance']['host']
 
517
        if network_ref['multi_host'] and FLAGS.host != host:
 
518
            continue
 
519
        hosts.append(_host_lease(fixed_ref))
 
520
    return '\n'.join(hosts)
 
521
 
 
522
 
 
523
def get_dhcp_hosts(context, network_ref):
 
524
    """Get network's hosts config in dhcp-host format."""
 
525
    hosts = []
 
526
    for fixed_ref in db.network_get_associated_fixed_ips(context,
 
527
                                                         network_ref['id']):
 
528
        host = fixed_ref['instance']['host']
 
529
        if network_ref['multi_host'] and FLAGS.host != host:
 
530
            continue
 
531
        hosts.append(_host_dhcp(fixed_ref))
 
532
    return '\n'.join(hosts)
 
533
 
 
534
 
 
535
def get_dhcp_opts(context, network_ref):
 
536
    """Get network's hosts config in dhcp-opts format."""
 
537
    hosts = []
 
538
    ips_ref = db.network_get_associated_fixed_ips(context, network_ref['id'])
 
539
 
 
540
    if ips_ref:
 
541
        #set of instance ids
 
542
        instance_set = set([fixed_ip_ref['instance_id']
 
543
                            for fixed_ip_ref in ips_ref])
 
544
        default_gw_network_node = {}
 
545
        for instance_id in instance_set:
 
546
            vifs = db.virtual_interface_get_by_instance(context, instance_id)
 
547
            if vifs:
 
548
                #offer a default gateway to the first virtual interface
 
549
                default_gw_network_node[instance_id] = vifs[0]['network_id']
 
550
 
 
551
        for fixed_ip_ref in ips_ref:
 
552
            instance_id = fixed_ip_ref['instance_id']
 
553
            if instance_id in default_gw_network_node:
 
554
                target_network_id = default_gw_network_node[instance_id]
 
555
                # we don't want default gateway for this fixed ip
 
556
                if target_network_id != fixed_ip_ref['network_id']:
 
557
                    hosts.append(_host_dhcp_opts(fixed_ip_ref))
 
558
    return '\n'.join(hosts)
 
559
 
 
560
 
 
561
def release_dhcp(dev, address, mac_address):
 
562
    utils.execute('dhcp_release', dev, address, mac_address, run_as_root=True)
 
563
 
 
564
 
 
565
def _add_dnsmasq_accept_rules(dev):
 
566
    """Allow DHCP and DNS traffic through to dnsmasq."""
 
567
    table = iptables_manager.ipv4['filter']
 
568
    for port in [67, 53]:
 
569
        for proto in ['udp', 'tcp']:
 
570
            args = {'dev': dev, 'port': port, 'proto': proto}
 
571
            table.add_rule('INPUT',
 
572
                           '-i %(dev)s -p %(proto)s -m %(proto)s '
 
573
                           '--dport %(port)s -j ACCEPT' % args)
 
574
    iptables_manager.apply()
 
575
 
 
576
 
 
577
# NOTE(ja): Sending a HUP only reloads the hostfile, so any
 
578
#           configuration options (like dchp-range, vlan, ...)
 
579
#           aren't reloaded.
 
580
@utils.synchronized('dnsmasq_start')
 
581
def update_dhcp(context, dev, network_ref):
 
582
    """(Re)starts a dnsmasq server for a given network.
 
583
 
 
584
    If a dnsmasq instance is already running then send a HUP
 
585
    signal causing it to reload, otherwise spawn a new instance.
 
586
 
 
587
    """
 
588
    conffile = _dhcp_file(dev, 'conf')
 
589
    with open(conffile, 'w') as f:
 
590
        f.write(get_dhcp_hosts(context, network_ref))
 
591
 
 
592
    if FLAGS.use_single_default_gateway:
 
593
        optsfile = _dhcp_file(dev, 'opts')
 
594
        with open(optsfile, 'w') as f:
 
595
            f.write(get_dhcp_opts(context, network_ref))
 
596
        os.chmod(optsfile, 0644)
 
597
 
 
598
    # Make sure dnsmasq can actually read it (it setuid()s to "nobody")
 
599
    os.chmod(conffile, 0644)
 
600
 
 
601
    pid = _dnsmasq_pid_for(dev)
 
602
 
 
603
    # if dnsmasq is already running, then tell it to reload
 
604
    if pid:
 
605
        out, _err = _execute('cat', '/proc/%d/cmdline' % pid,
 
606
                             check_exit_code=False)
 
607
        if conffile in out:
 
608
            try:
 
609
                _execute('kill', '-HUP', pid, run_as_root=True)
 
610
                return
 
611
            except Exception as exc:  # pylint: disable=W0703
 
612
                LOG.debug(_('Hupping dnsmasq threw %s'), exc)
 
613
        else:
 
614
            LOG.debug(_('Pid %d is stale, relaunching dnsmasq'), pid)
 
615
 
 
616
    cmd = ['FLAGFILE=%s' % FLAGS.dhcpbridge_flagfile,
 
617
           'NETWORK_ID=%s' % str(network_ref['id']),
 
618
           'dnsmasq',
 
619
           '--strict-order',
 
620
           '--bind-interfaces',
 
621
           '--conf-file=%s' % FLAGS.dnsmasq_config_file,
 
622
           '--domain=%s' % FLAGS.dhcp_domain,
 
623
           '--pid-file=%s' % _dhcp_file(dev, 'pid'),
 
624
           '--listen-address=%s' % network_ref['dhcp_server'],
 
625
           '--except-interface=lo',
 
626
           '--dhcp-range=%s,static,120s' % network_ref['dhcp_start'],
 
627
           '--dhcp-lease-max=%s' % len(netaddr.IPNetwork(network_ref['cidr'])),
 
628
           '--dhcp-hostsfile=%s' % _dhcp_file(dev, 'conf'),
 
629
           '--dhcp-script=%s' % FLAGS.dhcpbridge,
 
630
           '--leasefile-ro']
 
631
    if FLAGS.dns_server:
 
632
        cmd += ['-h', '-R', '--server=%s' % FLAGS.dns_server]
 
633
 
 
634
    if FLAGS.use_single_default_gateway:
 
635
        cmd += ['--dhcp-optsfile=%s' % _dhcp_file(dev, 'opts')]
 
636
 
 
637
    _execute(*cmd, run_as_root=True)
 
638
 
 
639
    _add_dnsmasq_accept_rules(dev)
 
640
 
 
641
 
 
642
@utils.synchronized('radvd_start')
 
643
def update_ra(context, dev, network_ref):
 
644
    conffile = _ra_file(dev, 'conf')
 
645
    with open(conffile, 'w') as f:
 
646
        conf_str = """
 
647
interface %s
 
648
{
 
649
   AdvSendAdvert on;
 
650
   MinRtrAdvInterval 3;
 
651
   MaxRtrAdvInterval 10;
 
652
   prefix %s
 
653
   {
 
654
        AdvOnLink on;
 
655
        AdvAutonomous on;
 
656
   };
 
657
};
 
658
""" % (dev, network_ref['cidr_v6'])
 
659
        f.write(conf_str)
 
660
 
 
661
    # Make sure radvd can actually read it (it setuid()s to "nobody")
 
662
    os.chmod(conffile, 0644)
 
663
 
 
664
    pid = _ra_pid_for(dev)
 
665
 
 
666
    # if radvd is already running, then tell it to reload
 
667
    if pid:
 
668
        out, _err = _execute('cat', '/proc/%d/cmdline'
 
669
                             % pid, check_exit_code=False)
 
670
        if conffile in out:
 
671
            try:
 
672
                _execute('kill', pid, run_as_root=True)
 
673
            except Exception as exc:  # pylint: disable=W0703
 
674
                LOG.debug(_('killing radvd threw %s'), exc)
 
675
        else:
 
676
            LOG.debug(_('Pid %d is stale, relaunching radvd'), pid)
 
677
 
 
678
    cmd = ['radvd',
 
679
           '-C', '%s' % _ra_file(dev, 'conf'),
 
680
           '-p', '%s' % _ra_file(dev, 'pid')]
 
681
 
 
682
    _execute(*cmd, run_as_root=True)
 
683
 
 
684
 
 
685
def _host_lease(fixed_ip_ref):
 
686
    """Return a host string for an address in leasefile format."""
 
687
    instance_ref = fixed_ip_ref['instance']
 
688
    if instance_ref['updated_at']:
 
689
        timestamp = instance_ref['updated_at']
 
690
    else:
 
691
        timestamp = instance_ref['created_at']
 
692
 
 
693
    seconds_since_epoch = calendar.timegm(timestamp.utctimetuple())
 
694
 
 
695
    return '%d %s %s %s *' % (seconds_since_epoch + FLAGS.dhcp_lease_time,
 
696
                              fixed_ip_ref['virtual_interface']['address'],
 
697
                              fixed_ip_ref['address'],
 
698
                              instance_ref['hostname'] or '*')
 
699
 
 
700
 
 
701
def _host_dhcp_network(fixed_ip_ref):
 
702
    instance_ref = fixed_ip_ref['instance']
 
703
    return 'NW-i%08d-%s' % (instance_ref['id'],
 
704
                            fixed_ip_ref['network_id'])
 
705
 
 
706
 
 
707
def _host_dhcp(fixed_ip_ref):
 
708
    """Return a host string for an address in dhcp-host format."""
 
709
    instance_ref = fixed_ip_ref['instance']
 
710
    vif = fixed_ip_ref['virtual_interface']
 
711
    if FLAGS.use_single_default_gateway:
 
712
        return '%s,%s.%s,%s,%s' % (vif['address'],
 
713
                               instance_ref['hostname'],
 
714
                               FLAGS.dhcp_domain,
 
715
                               fixed_ip_ref['address'],
 
716
                               "net:" + _host_dhcp_network(fixed_ip_ref))
 
717
    else:
 
718
        return '%s,%s.%s,%s' % (vif['address'],
 
719
                               instance_ref['hostname'],
 
720
                               FLAGS.dhcp_domain,
 
721
                               fixed_ip_ref['address'])
 
722
 
 
723
 
 
724
def _host_dhcp_opts(fixed_ip_ref):
 
725
    """Return a host string for an address in dhcp-host format."""
 
726
    return '%s,%s' % (_host_dhcp_network(fixed_ip_ref), 3)
 
727
 
 
728
 
 
729
def _execute(*cmd, **kwargs):
 
730
    """Wrapper around utils._execute for fake_network."""
 
731
    if FLAGS.fake_network:
 
732
        LOG.debug('FAKE NET: %s', ' '.join(map(str, cmd)))
 
733
        return 'fake', 0
 
734
    else:
 
735
        return utils.execute(*cmd, **kwargs)
 
736
 
 
737
 
 
738
def _device_exists(device):
 
739
    """Check if ethernet device exists."""
 
740
    (_out, err) = _execute('ip', 'link', 'show', 'dev', device,
 
741
                           check_exit_code=False)
 
742
    return not err
 
743
 
 
744
 
 
745
def _stop_dnsmasq(dev):
 
746
    """Stops the dnsmasq instance for a given network."""
 
747
    pid = _dnsmasq_pid_for(dev)
 
748
 
 
749
    if pid:
 
750
        try:
 
751
            _execute('kill', '-TERM', pid, run_as_root=True)
 
752
        except Exception as exc:  # pylint: disable=W0703
 
753
            LOG.debug(_('Killing dnsmasq threw %s'), exc)
 
754
 
 
755
 
 
756
def _dhcp_file(dev, kind):
 
757
    """Return path to a pid, leases or conf file for a bridge/device."""
 
758
    if not os.path.exists(FLAGS.networks_path):
 
759
        os.makedirs(FLAGS.networks_path)
 
760
    return os.path.abspath('%s/nova-%s.%s' % (FLAGS.networks_path,
 
761
                                              dev,
 
762
                                              kind))
 
763
 
 
764
 
 
765
def _ra_file(dev, kind):
 
766
    """Return path to a pid or conf file for a bridge/device."""
 
767
 
 
768
    if not os.path.exists(FLAGS.networks_path):
 
769
        os.makedirs(FLAGS.networks_path)
 
770
    return os.path.abspath('%s/nova-ra-%s.%s' % (FLAGS.networks_path,
 
771
                                              dev,
 
772
                                              kind))
 
773
 
 
774
 
 
775
def _dnsmasq_pid_for(dev):
 
776
    """Returns the pid for prior dnsmasq instance for a bridge/device.
 
777
 
 
778
    Returns None if no pid file exists.
 
779
 
 
780
    If machine has rebooted pid might be incorrect (caller should check).
 
781
 
 
782
    """
 
783
    pid_file = _dhcp_file(dev, 'pid')
 
784
 
 
785
    if os.path.exists(pid_file):
 
786
        with open(pid_file, 'r') as f:
 
787
            return int(f.read())
 
788
 
 
789
 
 
790
def _ra_pid_for(dev):
 
791
    """Returns the pid for prior radvd instance for a bridge/device.
 
792
 
 
793
    Returns None if no pid file exists.
 
794
 
 
795
    If machine has rebooted pid might be incorrect (caller should check).
 
796
 
 
797
    """
 
798
    pid_file = _ra_file(dev, 'pid')
 
799
 
 
800
    if os.path.exists(pid_file):
 
801
        with open(pid_file, 'r') as f:
 
802
            return int(f.read())
 
803
 
 
804
 
 
805
def _ip_bridge_cmd(action, params, device):
 
806
    """Build commands to add/del ips to bridges/devices."""
 
807
    cmd = ['ip', 'addr', action]
 
808
    cmd.extend(params)
 
809
    cmd.extend(['dev', device])
 
810
    return cmd
 
811
 
 
812
 
 
813
# Similar to compute virt layers, the Linux network node
 
814
# code uses a flexible driver model to support different ways
 
815
# of creating ethernet interfaces and attaching them to the network.
 
816
# In the case of a network host, these interfaces
 
817
# act as gateway/dhcp/vpn/etc. endpoints not VM interfaces.
 
818
 
 
819
 
 
820
def plug(network, mac_address):
 
821
    return interface_driver.plug(network, mac_address)
 
822
 
 
823
 
 
824
def unplug(network):
 
825
    return interface_driver.unplug(network)
 
826
 
 
827
 
 
828
def get_dev(network):
 
829
    return interface_driver.get_dev(network)
 
830
 
 
831
 
 
832
class LinuxNetInterfaceDriver(object):
 
833
    """Abstract class that defines generic network host API"""
 
834
    """ for for all Linux interface drivers."""
 
835
 
 
836
    def plug(self, network, mac_address):
 
837
        """Create Linux device, return device name"""
 
838
        raise NotImplementedError()
 
839
 
 
840
    def unplug(self, network):
 
841
        """Destory Linux device, return device name"""
 
842
        raise NotImplementedError()
 
843
 
 
844
    def get_dev(self, network):
 
845
        """Get device name"""
 
846
        raise NotImplementedError()
 
847
 
 
848
 
 
849
# plugs interfaces using Linux Bridge
 
850
class LinuxBridgeInterfaceDriver(LinuxNetInterfaceDriver):
 
851
 
 
852
    def plug(self, network, mac_address):
 
853
        if network.get('vlan', None) is not None:
 
854
            LinuxBridgeInterfaceDriver.ensure_vlan_bridge(
 
855
                           network['vlan'],
 
856
                           network['bridge'],
 
857
                           network['bridge_interface'],
 
858
                           network,
 
859
                           mac_address)
 
860
        else:
 
861
            LinuxBridgeInterfaceDriver.ensure_bridge(
 
862
                          network['bridge'],
 
863
                          network['bridge_interface'],
 
864
                          network)
 
865
 
 
866
        return network['bridge']
 
867
 
 
868
    def unplug(self, network):
 
869
        return self.get_dev(network)
 
870
 
 
871
    def get_dev(self, network):
 
872
        return network['bridge']
 
873
 
 
874
    @classmethod
 
875
    def ensure_vlan_bridge(_self, vlan_num, bridge, bridge_interface,
 
876
                                            net_attrs=None, mac_address=None):
 
877
        """Create a vlan and bridge unless they already exist."""
 
878
        interface = LinuxBridgeInterfaceDriver.ensure_vlan(vlan_num,
 
879
                                               bridge_interface, mac_address)
 
880
        LinuxBridgeInterfaceDriver.ensure_bridge(bridge, interface, net_attrs)
 
881
        return interface
 
882
 
 
883
    @classmethod
 
884
    @utils.synchronized('ensure_vlan', external=True)
 
885
    def ensure_vlan(_self, vlan_num, bridge_interface, mac_address=None):
 
886
        """Create a vlan unless it already exists."""
 
887
        interface = 'vlan%s' % vlan_num
 
888
        if not _device_exists(interface):
 
889
            LOG.debug(_('Starting VLAN inteface %s'), interface)
 
890
            _execute('vconfig', 'set_name_type',
 
891
                     'VLAN_PLUS_VID_NO_PAD', run_as_root=True)
 
892
            _execute('vconfig', 'add', bridge_interface,
 
893
                        vlan_num, run_as_root=True)
 
894
            # (danwent) the bridge will inherit this address, so we want to
 
895
            # make sure it is the value set from the NetworkManager
 
896
            if mac_address:
 
897
                _execute('ip', 'link', 'set', interface, "address",
 
898
                            mac_address, run_as_root=True)
 
899
            _execute('ip', 'link', 'set', interface, 'up', run_as_root=True)
 
900
        return interface
 
901
 
 
902
    @classmethod
 
903
    @utils.synchronized('ensure_bridge', external=True)
 
904
    def ensure_bridge(_self, bridge, interface, net_attrs=None):
 
905
        """Create a bridge unless it already exists.
 
906
 
 
907
        :param interface: the interface to create the bridge on.
 
908
        :param net_attrs: dictionary with  attributes used to create bridge.
 
909
 
 
910
        If net_attrs is set, it will add the net_attrs['gateway'] to the bridge
 
911
        using net_attrs['broadcast'] and net_attrs['cidr'].  It will also add
 
912
        the ip_v6 address specified in net_attrs['cidr_v6'] if use_ipv6 is set.
 
913
 
 
914
        The code will attempt to move any ips that already exist on the
 
915
        interface onto the bridge and reset the default gateway if necessary.
 
916
 
 
917
        """
 
918
        if not _device_exists(bridge):
 
919
            LOG.debug(_('Starting Bridge interface for %s'), interface)
 
920
            _execute('brctl', 'addbr', bridge, run_as_root=True)
 
921
            _execute('brctl', 'setfd', bridge, 0, run_as_root=True)
 
922
            # _execute('brctl setageing %s 10' % bridge, run_as_root=True)
 
923
            _execute('brctl', 'stp', bridge, 'off', run_as_root=True)
 
924
            # (danwent) bridge device MAC address can't be set directly.
 
925
            # instead it inherits the MAC address of the first device on the
 
926
            # bridge, which will either be the vlan interface, or a
 
927
            # physical NIC.
 
928
            _execute('ip', 'link', 'set', bridge, 'up', run_as_root=True)
 
929
 
 
930
        if interface:
 
931
            out, err = _execute('brctl', 'addif', bridge, interface,
 
932
                            check_exit_code=False, run_as_root=True)
 
933
 
 
934
            # NOTE(vish): This will break if there is already an ip on the
 
935
            #             interface, so we move any ips to the bridge
 
936
            gateway = None
 
937
            out, err = _execute('route', '-n', run_as_root=True)
 
938
            for line in out.split('\n'):
 
939
                fields = line.split()
 
940
                if fields and fields[0] == '0.0.0.0' and \
 
941
                                fields[-1] == interface:
 
942
                    gateway = fields[1]
 
943
                    _execute('route', 'del', 'default', 'gw', gateway,
 
944
                             'dev', interface, check_exit_code=False,
 
945
                             run_as_root=True)
 
946
            out, err = _execute('ip', 'addr', 'show', 'dev', interface,
 
947
                                'scope', 'global', run_as_root=True)
 
948
            for line in out.split('\n'):
 
949
                fields = line.split()
 
950
                if fields and fields[0] == 'inet':
 
951
                    params = fields[1:-1]
 
952
                    _execute(*_ip_bridge_cmd('del', params, fields[-1]),
 
953
                                run_as_root=True)
 
954
                    _execute(*_ip_bridge_cmd('add', params, bridge),
 
955
                                run_as_root=True)
 
956
            if gateway:
 
957
                _execute('route', 'add', 'default', 'gw', gateway,
 
958
                            run_as_root=True)
 
959
 
 
960
            if (err and err != "device %s is already a member of a bridge;"
 
961
                     "can't enslave it to bridge %s.\n" % (interface, bridge)):
 
962
                raise exception.Error('Failed to add interface: %s' % err)
 
963
 
 
964
        iptables_manager.ipv4['filter'].add_rule('FORWARD',
 
965
                                             '--in-interface %s -j ACCEPT' % \
 
966
                                             bridge)
 
967
        iptables_manager.ipv4['filter'].add_rule('FORWARD',
 
968
                                             '--out-interface %s -j ACCEPT' % \
 
969
                                             bridge)
 
970
 
 
971
 
 
972
# plugs interfaces using Open vSwitch
 
973
class LinuxOVSInterfaceDriver(LinuxNetInterfaceDriver):
 
974
 
 
975
    def plug(self, network, mac_address):
 
976
        dev = "gw-" + str(network['id'])
 
977
        if not _device_exists(dev):
 
978
            bridge = FLAGS.linuxnet_ovs_integration_bridge
 
979
            _execute('ovs-vsctl',
 
980
                        '--', '--may-exist', 'add-port', bridge, dev,
 
981
                        '--', 'set', 'Interface', dev, "type=internal",
 
982
                        '--', 'set', 'Interface', dev,
 
983
                                "external-ids:iface-id=nova-%s" % dev,
 
984
                        '--', 'set', 'Interface', dev,
 
985
                                "external-ids:iface-status=active",
 
986
                        '--', 'set', 'Interface', dev,
 
987
                                "external-ids:attached-mac=%s" % mac_address,
 
988
                        run_as_root=True)
 
989
            _execute('ip', 'link', 'set', dev, "address", mac_address,
 
990
                        run_as_root=True)
 
991
            _execute('ip', 'link', 'set', dev, 'up', run_as_root=True)
 
992
 
 
993
        return dev
 
994
 
 
995
    def unplug(self, network):
 
996
        return self.get_dev(network)
 
997
 
 
998
    def get_dev(self, network):
 
999
        dev = "gw-" + str(network['id'])
 
1000
        return dev
 
1001
 
 
1002
iptables_manager = IptablesManager()
 
1003
interface_driver = utils.import_object(FLAGS.linuxnet_interface_driver)