Files
nova/nova/network/linux_net.py
T
2010-12-16 22:52:08 +00:00

304 lines
11 KiB
Python

# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Implements vlans, bridges, and iptables rules using linux utilities.
"""
import logging
import os
import signal
# TODO(ja): does the definition of network_path belong here?
from nova import db
from nova import flags
from nova import utils
def _bin_file(script):
"""Return the absolute path to scipt in the bin directory"""
return os.path.abspath(os.path.join(__file__, "../../../bin", script))
FLAGS = flags.FLAGS
flags.DEFINE_string('dhcpbridge_flagfile',
'/etc/nova/nova-dhcpbridge.conf',
'location of flagfile for dhcpbridge')
flags.DEFINE_string('networks_path', '$state_path/networks',
'Location to keep network config files')
flags.DEFINE_string('public_interface', 'vlan1',
'Interface for public IP addresses')
flags.DEFINE_string('vlan_interface', 'eth0',
'network device for vlans')
flags.DEFINE_string('dhcpbridge', _bin_file('nova-dhcpbridge'),
'location of nova-dhcpbridge')
flags.DEFINE_string('cc_host', utils.get_my_ip(), 'ip of api server')
flags.DEFINE_string('routing_source_ip', '127.0.0.1',
'Public IP of network host')
flags.DEFINE_bool('use_nova_chains', False,
'use the nova_ routing chains instead of default')
DEFAULT_PORTS = [("tcp", 80), ("tcp", 22), ("udp", 1194), ("tcp", 443)]
def metadata_forward():
"""Create forwarding rule for metadata"""
_confirm_rule("PREROUTING", "-t nat -s 0.0.0.0/0 "
"-d 169.254.169.254/32 -p tcp -m tcp --dport 80 -j DNAT "
"--to-destination %s:%s" % (FLAGS.cc_host, FLAGS.cc_port))
def init_host():
"""Basic networking setup goes here"""
# NOTE(devcamcar): Cloud public SNAT entries and the default
# SNAT rule for outbound traffic.
_confirm_rule("POSTROUTING", "-t nat -s %s "
"-j SNAT --to-source %s"
% (FLAGS.fixed_range, FLAGS.routing_source_ip))
_confirm_rule("POSTROUTING", "-t nat -s %s -j MASQUERADE" %
FLAGS.fixed_range)
_confirm_rule("POSTROUTING", "-t nat -s %(range)s -d %(range)s -j ACCEPT" %
{'range': FLAGS.fixed_range})
def bind_floating_ip(floating_ip):
"""Bind ip to public interface"""
_execute("sudo ip addr add %s dev %s" % (floating_ip,
FLAGS.public_interface))
def unbind_floating_ip(floating_ip):
"""Unbind a public ip from public interface"""
_execute("sudo ip addr del %s dev %s" % (floating_ip,
FLAGS.public_interface))
def ensure_vlan_forward(public_ip, port, private_ip):
"""Sets up forwarding rules for vlan"""
_confirm_rule("FORWARD", "-d %s -p udp --dport 1194 -j ACCEPT" %
private_ip)
_confirm_rule("PREROUTING",
"-t nat -d %s -p udp --dport %s -j DNAT --to %s:1194"
% (public_ip, port, private_ip))
def ensure_floating_forward(floating_ip, fixed_ip):
"""Ensure floating ip forwarding rule"""
_confirm_rule("PREROUTING", "-t nat -d %s -j DNAT --to %s"
% (floating_ip, fixed_ip))
_confirm_rule("POSTROUTING", "-t nat -s %s -j SNAT --to %s"
% (fixed_ip, floating_ip))
# TODO(joshua): Get these from the secgroup datastore entries
_confirm_rule("FORWARD", "-d %s -p icmp -j ACCEPT"
% (fixed_ip))
for (protocol, port) in DEFAULT_PORTS:
_confirm_rule("FORWARD", "-d %s -p %s --dport %s -j ACCEPT"
% (fixed_ip, protocol, port))
def remove_floating_forward(floating_ip, fixed_ip):
"""Remove forwarding for floating ip"""
_remove_rule("PREROUTING", "-t nat -d %s -j DNAT --to %s"
% (floating_ip, fixed_ip))
_remove_rule("POSTROUTING", "-t nat -s %s -j SNAT --to %s"
% (fixed_ip, floating_ip))
_remove_rule("FORWARD", "-d %s -p icmp -j ACCEPT"
% (fixed_ip))
for (protocol, port) in DEFAULT_PORTS:
_remove_rule("FORWARD", "-d %s -p %s --dport %s -j ACCEPT"
% (fixed_ip, protocol, port))
def ensure_vlan_bridge(vlan_num, bridge, net_attrs=None):
"""Create a vlan and bridge unless they already exist"""
interface = ensure_vlan(vlan_num)
ensure_bridge(bridge, interface, net_attrs)
def ensure_vlan(vlan_num):
"""Create a vlan unless it already exists"""
interface = "vlan%s" % vlan_num
if not _device_exists(interface):
logging.debug("Starting VLAN inteface %s", interface)
_execute("sudo vconfig set_name_type VLAN_PLUS_VID_NO_PAD")
_execute("sudo vconfig add %s %s" % (FLAGS.vlan_interface, vlan_num))
_execute("sudo ifconfig %s up" % interface)
return interface
def ensure_bridge(bridge, interface, net_attrs=None):
"""Create a bridge unless it already exists"""
if not _device_exists(bridge):
logging.debug("Starting Bridge interface for %s", interface)
_execute("sudo brctl addbr %s" % bridge)
_execute("sudo brctl setfd %s 0" % bridge)
# _execute("sudo brctl setageing %s 10" % bridge)
_execute("sudo brctl stp %s off" % bridge)
if interface:
_execute("sudo brctl addif %s %s" % (bridge, interface))
if net_attrs:
_execute("sudo ifconfig %s %s broadcast %s netmask %s up" % \
(bridge,
net_attrs['gateway'],
net_attrs['broadcast'],
net_attrs['netmask']))
else:
_execute("sudo ifconfig %s up" % bridge)
_confirm_rule("FORWARD", "--in-interface %s -j ACCEPT" % bridge)
_confirm_rule("FORWARD", "--out-interface %s -j ACCEPT" % bridge)
def get_dhcp_hosts(context, network_id):
"""Get a string containing a network's hosts config in dnsmasq format"""
hosts = []
for fixed_ip_ref in db.network_get_associated_fixed_ips(context,
network_id):
hosts.append(_host_dhcp(fixed_ip_ref))
return '\n'.join(hosts)
# NOTE(ja): Sending a HUP only reloads the hostfile, so any
# configuration options (like dchp-range, vlan, ...)
# aren't reloaded.
def update_dhcp(context, network_id):
"""(Re)starts a dnsmasq server for a given network
if a dnsmasq instance is already running then send a HUP
signal causing it to reload, otherwise spawn a new instance
"""
network_ref = db.network_get(context, network_id)
conffile = _dhcp_file(network_ref['bridge'], 'conf')
with open(conffile, 'w') as f:
f.write(get_dhcp_hosts(context, network_id))
# Make sure dnsmasq can actually read it (it setuid()s to "nobody")
os.chmod(conffile, 0644)
pid = _dnsmasq_pid_for(network_ref['bridge'])
# if dnsmasq is already running, then tell it to reload
if pid:
out, _err = _execute('cat /proc/%d/cmdline' % pid,
check_exit_code=False)
if conffile in out:
try:
_execute('sudo kill -HUP %d' % pid)
return
except Exception as exc: # pylint: disable-msg=W0703
logging.debug("Hupping dnsmasq threw %s", exc)
else:
logging.debug("Pid %d is stale, relaunching dnsmasq", pid)
# FLAGFILE and DNSMASQ_INTERFACE in env
env = {'FLAGFILE': FLAGS.dhcpbridge_flagfile,
'DNSMASQ_INTERFACE': network_ref['bridge']}
command = _dnsmasq_cmd(network_ref)
_execute(command, addl_env=env)
def _host_dhcp(fixed_ip_ref):
"""Return a host string for an address"""
instance_ref = fixed_ip_ref['instance']
return "%s,%s.novalocal,%s" % (instance_ref['mac_address'],
instance_ref['hostname'],
fixed_ip_ref['address'])
def _execute(cmd, *args, **kwargs):
"""Wrapper around utils._execute for fake_network"""
if FLAGS.fake_network:
logging.debug("FAKE NET: %s", cmd)
return "fake", 0
else:
return utils.execute(cmd, *args, **kwargs)
def _device_exists(device):
"""Check if ethernet device exists"""
(_out, err) = _execute("ifconfig %s" % device, check_exit_code=False)
return not err
def _confirm_rule(chain, cmd):
"""Delete and re-add iptables rule"""
if FLAGS.use_nova_chains:
chain = "nova_%s" % chain.lower()
_execute("sudo iptables --delete %s %s" % (chain, cmd),
check_exit_code=False)
_execute("sudo iptables -I %s %s" % (chain, cmd))
def _remove_rule(chain, cmd):
"""Remove iptables rule"""
if FLAGS.use_nova_chains:
chain = "%s" % chain.lower()
_execute("sudo iptables --delete %s %s" % (chain, cmd))
def _dnsmasq_cmd(net):
"""Builds dnsmasq command"""
cmd = ['sudo -E dnsmasq',
' --strict-order',
' --bind-interfaces',
' --conf-file=',
' --pid-file=%s' % _dhcp_file(net['bridge'], 'pid'),
' --listen-address=%s' % net['gateway'],
' --except-interface=lo',
' --dhcp-range=%s,static,120s' % net['dhcp_start'],
' --dhcp-hostsfile=%s' % _dhcp_file(net['bridge'], 'conf'),
' --dhcp-script=%s' % FLAGS.dhcpbridge,
' --leasefile-ro']
return ''.join(cmd)
def _stop_dnsmasq(network):
"""Stops the dnsmasq instance for a given network"""
pid = _dnsmasq_pid_for(network)
if pid:
try:
_execute('sudo kill -TERM %d' % pid)
except Exception as exc: # pylint: disable-msg=W0703
logging.debug("Killing dnsmasq threw %s", exc)
def _dhcp_file(bridge, kind):
"""Return path to a pid, leases or conf file for a bridge"""
if not os.path.exists(FLAGS.networks_path):
os.makedirs(FLAGS.networks_path)
return os.path.abspath("%s/nova-%s.%s" % (FLAGS.networks_path,
bridge,
kind))
def _dnsmasq_pid_for(bridge):
"""Returns the pid for prior dnsmasq instance for a bridge
Returns None if no pid file exists
If machine has rebooted pid might be incorrect (caller should check)
"""
pid_file = _dhcp_file(bridge, 'pid')
if os.path.exists(pid_file):
with open(pid_file, 'r') as f:
return int(f.read())