1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
3
# Copyright 2010 United States Government as represented by the
4
# Administrator of the National Aeronautics and Space Administration.
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
11
# http://www.apache.org/licenses/LICENSE-2.0
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
19
"""Implements vlans, bridges, and iptables rules using linux utilities."""
27
from nova import exception
28
from nova import flags
29
from nova import log as logging
30
from nova import utils
33
LOG = logging.getLogger("nova.linux_net")
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))
42
flags.DEFINE_string('dhcpbridge_flagfile',
43
'/etc/nova/nova-dhcpbridge.conf',
44
'location of flagfile for dhcpbridge')
45
flags.DEFINE_string('dhcp_domain',
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])
79
class IptablesRule(object):
82
You shouldn't need to use this class directly, it's only used by
87
def __init__(self, chain, rule, wrap=True, top=False):
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))
99
def __ne__(self, other):
100
return not self == other
104
chain = '%s-%s' % (binary_name, self.chain)
107
return '-A %s %s' % (chain, self.rule)
110
class IptablesTable(object):
111
"""An iptables table."""
116
self.unwrapped_chains = set()
118
def add_chain(self, name, wrap=True):
119
"""Adds a named chain to the table.
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.
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'.
131
self.chains.add(name)
133
self.unwrapped_chains.add(name)
135
def remove_chain(self, name, wrap=True):
136
"""Remove named chain.
138
This removal "cascades". All rule in the chain are removed, as are
139
all rules in other chains that jump to it.
141
If the chain is not found, this is merely logged.
145
chain_set = self.chains
147
chain_set = self.unwrapped_chains
149
if name not in chain_set:
150
LOG.debug(_('Attempted to remove chain %s which does not exist'),
154
chain_set.remove(name)
155
self.rules = filter(lambda r: r.chain != name, self.rules)
158
jump_snippet = '-j %s-%s' % (binary_name, name)
160
jump_snippet = '-j %s' % (name,)
162
self.rules = filter(lambda r: jump_snippet not in r.rule, self.rules)
164
def add_rule(self, chain, rule, wrap=True, top=False):
165
"""Add a rule to the table.
167
This is just like what you'd feed to iptables, just without
168
the '-A <chain name>' bit at the start.
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.
175
if wrap and chain not in self.chains:
176
raise ValueError(_('Unknown chain: %r') % chain)
179
rule = ' '.join(map(self._wrap_target_chain, rule.split(' ')))
181
self.rules.append(IptablesRule(chain, rule, wrap, top))
183
def _wrap_target_chain(self, s):
184
if s.startswith('$'):
185
return '%s-%s' % (binary_name, s[1:])
188
def remove_rule(self, chain, rule, wrap=True, top=False):
189
"""Remove a rule from a chain.
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
197
self.rules.remove(IptablesRule(chain, rule, wrap, top))
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})
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)
212
class IptablesManager(object):
213
"""Wrapper for iptables.
215
See IptablesTable for some usage docs
217
A number of chains are set up to begin with.
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.
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.
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.
235
def __init__(self, execute=None):
237
self.execute = _execute
239
self.execute = execute
241
self.ipv4 = {'filter': IptablesTable(),
242
'nat': IptablesTable()}
243
self.ipv6 = {'filter': IptablesTable()}
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)
255
tables['filter'].add_chain('local')
256
tables['filter'].add_rule('nova-filter-top', '-j $local',
259
# Wrap the builtin chains
260
builtin_chains = {4: {'filter': ['INPUT', 'OUTPUT', 'FORWARD'],
261
'nat': ['PREROUTING', 'OUTPUT', 'POSTROUTING']},
262
6: {'filter': ['INPUT', 'OUTPUT', 'FORWARD']}}
264
for ip_version in builtin_chains:
267
elif ip_version == 6:
270
for table, chains in builtin_chains[ip_version].iteritems():
272
tables[table].add_chain(chain)
273
tables[table].add_rule(chain, '-j $%s' % (chain,),
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',
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',
289
# And then we add a floating-snat chain and jump to first thing in
291
self.ipv4['nat'].add_chain('floating-snat')
292
self.ipv4['nat'].add_rule('snat', '-j $floating-snat')
294
@utils.synchronized('iptables', external=True)
296
"""Apply the current in-memory set of iptables rules.
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.
303
s = [('iptables', self.ipv4)]
305
s += [('ip6tables', self.ipv6)]
307
for cmd, tables in s:
309
current_table, _ = self.execute('%s-save' % (cmd,),
310
'-t', '%s' % (table,),
313
current_lines = current_table.split('\n')
314
new_filter = self._modify_rules(current_lines,
316
self.execute('%s-restore' % (cmd,), run_as_root=True,
317
process_input='\n'.join(new_filter),
320
def _modify_rules(self, current_lines, table, binary=None):
321
unwrapped_chains = table.unwrapped_chains
322
chains = table.chains
325
# Remove any trace of our rules
326
new_filter = filter(lambda line: binary_name not in line,
331
for rules_index, rule in enumerate(new_filter):
333
if rule.startswith(':'):
336
if not rule.startswith(':'):
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(),
348
our_rules += [rule_str]
350
new_filter[rules_index:rules_index] = our_rules
352
new_filter[rules_index:rules_index] = [':%s - [0:0]' % \
354
for name in unwrapped_chains]
355
new_filter[rules_index:rules_index] = [':%s-%s - [0:0]' % \
356
(binary_name, name,) \
361
def _weed_out_duplicates(line):
363
if line in seen_lines:
369
# We filter duplicates, letting the *last* occurrence take
372
new_filter = filter(_weed_out_duplicates, new_filter)
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()
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' % \
394
FLAGS.routing_source_ip))
396
iptables_manager.ipv4['nat'].add_rule('POSTROUTING',
397
'-s %s -d %s -j ACCEPT' % \
398
(FLAGS.fixed_range, FLAGS.dmz_cidr))
400
iptables_manager.ipv4['nat'].add_rule('POSTROUTING',
401
'-s %(range)s -d %(range)s '
403
{'range': FLAGS.fixed_range})
404
iptables_manager.apply()
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)
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)
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)
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',
436
'-j ACCEPT' % private_ip)
437
iptables_manager.ipv4['nat'].add_rule('PREROUTING',
439
'--dport %s -j DNAT --to %s:1194' %
440
(public_ip, port, private_ip))
441
iptables_manager.ipv4['nat'].add_rule("OUTPUT",
443
"--dport %s -j DNAT --to %s:1194" %
444
(public_ip, port, private_ip))
445
iptables_manager.apply()
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()
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()
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)),
466
'-s %s -j SNAT --to %s' % (fixed_ip, floating_ip))]
469
def initialize_gateway_device(dev, network_ref):
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']]]
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),
492
for ip_params in new_ip_params:
493
_execute(*_ip_bridge_cmd('add', ip_params, dev),
495
if FLAGS.send_arp_for_ha:
496
_execute('arping', '-U', network_ref['dhcp_server'],
498
'-c', 1, run_as_root=True, check_exit_code=False)
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)
511
def get_dhcp_leases(context, network_ref):
512
"""Return a network's hosts config in dnsmasq leasefile format."""
514
for fixed_ref in db.network_get_associated_fixed_ips(context,
516
host = fixed_ref['instance']['host']
517
if network_ref['multi_host'] and FLAGS.host != host:
519
hosts.append(_host_lease(fixed_ref))
520
return '\n'.join(hosts)
523
def get_dhcp_hosts(context, network_ref):
524
"""Get network's hosts config in dhcp-host format."""
526
for fixed_ref in db.network_get_associated_fixed_ips(context,
528
host = fixed_ref['instance']['host']
529
if network_ref['multi_host'] and FLAGS.host != host:
531
hosts.append(_host_dhcp(fixed_ref))
532
return '\n'.join(hosts)
535
def get_dhcp_opts(context, network_ref):
536
"""Get network's hosts config in dhcp-opts format."""
538
ips_ref = db.network_get_associated_fixed_ips(context, network_ref['id'])
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)
548
#offer a default gateway to the first virtual interface
549
default_gw_network_node[instance_id] = vifs[0]['network_id']
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)
561
def release_dhcp(dev, address, mac_address):
562
utils.execute('dhcp_release', dev, address, mac_address, run_as_root=True)
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()
577
# NOTE(ja): Sending a HUP only reloads the hostfile, so any
578
# configuration options (like dchp-range, vlan, ...)
580
@utils.synchronized('dnsmasq_start')
581
def update_dhcp(context, dev, network_ref):
582
"""(Re)starts a dnsmasq server for a given network.
584
If a dnsmasq instance is already running then send a HUP
585
signal causing it to reload, otherwise spawn a new instance.
588
conffile = _dhcp_file(dev, 'conf')
589
with open(conffile, 'w') as f:
590
f.write(get_dhcp_hosts(context, network_ref))
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)
598
# Make sure dnsmasq can actually read it (it setuid()s to "nobody")
599
os.chmod(conffile, 0644)
601
pid = _dnsmasq_pid_for(dev)
603
# if dnsmasq is already running, then tell it to reload
605
out, _err = _execute('cat', '/proc/%d/cmdline' % pid,
606
check_exit_code=False)
609
_execute('kill', '-HUP', pid, run_as_root=True)
611
except Exception as exc: # pylint: disable=W0703
612
LOG.debug(_('Hupping dnsmasq threw %s'), exc)
614
LOG.debug(_('Pid %d is stale, relaunching dnsmasq'), pid)
616
cmd = ['FLAGFILE=%s' % FLAGS.dhcpbridge_flagfile,
617
'NETWORK_ID=%s' % str(network_ref['id']),
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,
632
cmd += ['-h', '-R', '--server=%s' % FLAGS.dns_server]
634
if FLAGS.use_single_default_gateway:
635
cmd += ['--dhcp-optsfile=%s' % _dhcp_file(dev, 'opts')]
637
_execute(*cmd, run_as_root=True)
639
_add_dnsmasq_accept_rules(dev)
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:
651
MaxRtrAdvInterval 10;
658
""" % (dev, network_ref['cidr_v6'])
661
# Make sure radvd can actually read it (it setuid()s to "nobody")
662
os.chmod(conffile, 0644)
664
pid = _ra_pid_for(dev)
666
# if radvd is already running, then tell it to reload
668
out, _err = _execute('cat', '/proc/%d/cmdline'
669
% pid, check_exit_code=False)
672
_execute('kill', pid, run_as_root=True)
673
except Exception as exc: # pylint: disable=W0703
674
LOG.debug(_('killing radvd threw %s'), exc)
676
LOG.debug(_('Pid %d is stale, relaunching radvd'), pid)
679
'-C', '%s' % _ra_file(dev, 'conf'),
680
'-p', '%s' % _ra_file(dev, 'pid')]
682
_execute(*cmd, run_as_root=True)
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']
691
timestamp = instance_ref['created_at']
693
seconds_since_epoch = calendar.timegm(timestamp.utctimetuple())
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 '*')
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'])
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'],
715
fixed_ip_ref['address'],
716
"net:" + _host_dhcp_network(fixed_ip_ref))
718
return '%s,%s.%s,%s' % (vif['address'],
719
instance_ref['hostname'],
721
fixed_ip_ref['address'])
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)
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)))
735
return utils.execute(*cmd, **kwargs)
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)
745
def _stop_dnsmasq(dev):
746
"""Stops the dnsmasq instance for a given network."""
747
pid = _dnsmasq_pid_for(dev)
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)
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,
765
def _ra_file(dev, kind):
766
"""Return path to a pid or conf file for a bridge/device."""
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,
775
def _dnsmasq_pid_for(dev):
776
"""Returns the pid for prior dnsmasq instance for a bridge/device.
778
Returns None if no pid file exists.
780
If machine has rebooted pid might be incorrect (caller should check).
783
pid_file = _dhcp_file(dev, 'pid')
785
if os.path.exists(pid_file):
786
with open(pid_file, 'r') as f:
790
def _ra_pid_for(dev):
791
"""Returns the pid for prior radvd instance for a bridge/device.
793
Returns None if no pid file exists.
795
If machine has rebooted pid might be incorrect (caller should check).
798
pid_file = _ra_file(dev, 'pid')
800
if os.path.exists(pid_file):
801
with open(pid_file, 'r') as f:
805
def _ip_bridge_cmd(action, params, device):
806
"""Build commands to add/del ips to bridges/devices."""
807
cmd = ['ip', 'addr', action]
809
cmd.extend(['dev', device])
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.
820
def plug(network, mac_address):
821
return interface_driver.plug(network, mac_address)
825
return interface_driver.unplug(network)
828
def get_dev(network):
829
return interface_driver.get_dev(network)
832
class LinuxNetInterfaceDriver(object):
833
"""Abstract class that defines generic network host API"""
834
""" for for all Linux interface drivers."""
836
def plug(self, network, mac_address):
837
"""Create Linux device, return device name"""
838
raise NotImplementedError()
840
def unplug(self, network):
841
"""Destory Linux device, return device name"""
842
raise NotImplementedError()
844
def get_dev(self, network):
845
"""Get device name"""
846
raise NotImplementedError()
849
# plugs interfaces using Linux Bridge
850
class LinuxBridgeInterfaceDriver(LinuxNetInterfaceDriver):
852
def plug(self, network, mac_address):
853
if network.get('vlan', None) is not None:
854
LinuxBridgeInterfaceDriver.ensure_vlan_bridge(
857
network['bridge_interface'],
861
LinuxBridgeInterfaceDriver.ensure_bridge(
863
network['bridge_interface'],
866
return network['bridge']
868
def unplug(self, network):
869
return self.get_dev(network)
871
def get_dev(self, network):
872
return network['bridge']
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)
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
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)
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.
907
:param interface: the interface to create the bridge on.
908
:param net_attrs: dictionary with attributes used to create bridge.
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.
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.
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
928
_execute('ip', 'link', 'set', bridge, 'up', run_as_root=True)
931
out, err = _execute('brctl', 'addif', bridge, interface,
932
check_exit_code=False, run_as_root=True)
934
# NOTE(vish): This will break if there is already an ip on the
935
# interface, so we move any ips to the bridge
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:
943
_execute('route', 'del', 'default', 'gw', gateway,
944
'dev', interface, check_exit_code=False,
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]),
954
_execute(*_ip_bridge_cmd('add', params, bridge),
957
_execute('route', 'add', 'default', 'gw', gateway,
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)
964
iptables_manager.ipv4['filter'].add_rule('FORWARD',
965
'--in-interface %s -j ACCEPT' % \
967
iptables_manager.ipv4['filter'].add_rule('FORWARD',
968
'--out-interface %s -j ACCEPT' % \
972
# plugs interfaces using Open vSwitch
973
class LinuxOVSInterfaceDriver(LinuxNetInterfaceDriver):
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,
989
_execute('ip', 'link', 'set', dev, "address", mac_address,
991
_execute('ip', 'link', 'set', dev, 'up', run_as_root=True)
995
def unplug(self, network):
996
return self.get_dev(network)
998
def get_dev(self, network):
999
dev = "gw-" + str(network['id'])
1002
iptables_manager = IptablesManager()
1003
interface_driver = utils.import_object(FLAGS.linuxnet_interface_driver)