Adds hooks for applying ovs flows when vifs are created and destroyed for XenServer instances.

This commit is contained in:
Cory Wright 2011-06-01 07:26:40 +00:00 committed by Tarmac
commit 8185703771
7 changed files with 393 additions and 19 deletions

View File

@ -0,0 +1,96 @@
#!/bin/bash
#
# openvswitch-nova
#
# chkconfig: 2345 96 89
# description: Apply initial OVS flows for Nova
# Copyright 2011 OpenStack LLC.
# Copyright (C) 2009, 2010, 2011 Nicira Networks, Inc.
# 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.
# source function library
if [ -f /etc/init.d/functions ]; then
. /etc/init.d/functions
elif [ -f /etc/rc.d/init.d/functions ]; then
. /etc/rc.d/init.d/functions
elif [ -f /lib/lsb/init-functions ]; then
. /lib/lsb/init-functions
else
echo "$0: missing LSB shell function library" >&2
exit 1
fi
OVS_CONFIGURE_BASE_FLOWS=/etc/xensource/scripts/ovs_configure_base_flows.py
if test -e /etc/sysconfig/openvswitch-nova; then
. /etc/sysconfig/openvswitch-nova
else
echo "$0: missing configuration file: /etc/sysconfig/openvswitch-nova"
exit 1
fi
if test -e /etc/xensource/network.conf; then
NETWORK_MODE=$(cat /etc/xensource/network.conf)
fi
case ${NETWORK_MODE:=openvswitch} in
vswitch|openvswitch)
;;
bridge)
exit 0
;;
*)
echo "Open vSwitch disabled (/etc/xensource/network.conf is invalid)" >&2
exit 0
;;
esac
function run_ovs_conf_base_flows {
# expected format: DEVICE_BRIDGES="eth0:xenbr0 eth1:xenbr1"
for pair in $DEVICE_BRIDGES; do
# below in $info, physical device is [0], bridge name is [1]
info=${pair//:/ }
/usr/bin/python $OVS_CONFIGURE_BASE_FLOWS $1 ${info[0]} ${info[1]}
done
}
function start {
run_ovs_conf_base_flows online
}
function stop {
run_ovs_conf_base_flows offline
}
function restart {
run_ovs_conf_base_flows reset
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
restart
;;
*)
echo "usage: openvswitch-nova [start|stop|restart]"
exit 1
;;
esac

View File

@ -0,0 +1 @@
#DEVICE_BRIDGES="eth0:xenbr0 eth1:xenbr1"

View File

@ -0,0 +1,3 @@
SUBSYSTEM=="xen-backend", KERNEL=="vif*", RUN+="/etc/xensource/scripts/ovs_configure_vif_flows.py $env{ACTION} %k all"
# is this one needed?
#SUBSYSTEM=="net", KERNEL=="tap*", RUN+="/etc/xensource/scripts/ovs_configure_vif_flows.py $env{ACTION} %k all"

View File

@ -0,0 +1,40 @@
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack LLC.
# 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.
import os
import subprocess
def execute_get_output(*command):
"""Execute and return stdout"""
devnull = open(os.devnull, 'w')
command = map(str, command)
proc = subprocess.Popen(command, close_fds=True,
stdout=subprocess.PIPE, stderr=devnull)
devnull.close()
return proc.stdout.read().strip()
def execute(*command):
"""Execute without returning stdout"""
devnull = open(os.devnull, 'w')
command = map(str, command)
proc = subprocess.Popen(command, close_fds=True,
stdout=subprocess.PIPE, stderr=devnull)
devnull.close()

View File

@ -0,0 +1,62 @@
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack LLC.
# 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.
"""
This script is used to configure base openvswitch flows for XenServer hosts.
"""
import os
import sys
from novalib import execute, execute_get_output
def main(command, phys_dev_name, bridge_name):
ovs_ofctl = lambda *rule: execute('/usr/bin/ovs-ofctl', *rule)
# always clear all flows first
ovs_ofctl('del-flows', bridge_name)
if command in ('online', 'reset'):
pnic_ofport = execute_get_output('/usr/bin/ovs-vsctl', 'get',
'Interface', phys_dev_name, 'ofport')
# these flows are lower priority than all VM-specific flows.
# allow all traffic from the physical NIC, as it is trusted (i.e.,
# from a filtered vif, or from the physical infrastructure)
ovs_ofctl('add-flow', bridge_name,
"priority=2,in_port=%s,actions=normal" % pnic_ofport)
# default drop
ovs_ofctl('add-flow', bridge_name, 'priority=1,actions=drop')
if __name__ == "__main__":
if len(sys.argv) != 4 or sys.argv[1] not in ('online', 'offline', 'reset'):
print sys.argv
script_name = os.path.basename(sys.argv[0])
print "This script configures base ovs flows."
print "usage: %s [online|offline|reset] phys-dev-name bridge-name" \
% script_name
print " ex: %s online eth0 xenbr0" % script_name
sys.exit(1)
else:
command, phys_dev_name, bridge_name = sys.argv[1:4]
main(command, phys_dev_name, bridge_name)

View File

@ -0,0 +1,180 @@
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack LLC.
# 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.
"""
This script is used to configure openvswitch flows on XenServer hosts.
"""
import os
import sys
# This is written to Python 2.4, since that is what is available on XenServer
import netaddr
import simplejson as json
from novalib import execute, execute_get_output
OVS_OFCTL = '/usr/bin/ovs-ofctl'
class OvsFlow(object):
def __init__(self, bridge, params):
self.bridge = bridge
self.params = params
def add(self, rule):
execute(OVS_OFCTL, 'add-flow', self.bridge, rule % self.params)
def clear_flows(self, ofport):
execute(OVS_OFCTL, 'del-flows', self.bridge, "in_port=%s" % ofport)
def main(command, vif_raw, net_type):
if command not in ('online', 'offline'):
return
vif_name, dom_id, vif_index = vif_raw.split('-')
vif = "%s%s.%s" % (vif_name, dom_id, vif_index)
bridge = "xenbr%s" % vif_index
xsls = execute_get_output('/usr/bin/xenstore-ls',
'/local/domain/%s/vm-data/networking' % dom_id)
macs = [line.split("=")[0].strip() for line in xsls.splitlines()]
for mac in macs:
xsread = execute_get_output('/usr/bin/xenstore-read',
'/local/domain/%s/vm-data/networking/%s' %
(dom_id, mac))
data = json.loads(xsread)
if data["label"] == "public":
this_vif = "vif%s.0" % dom_id
else:
this_vif = "vif%s.1" % dom_id
if vif == this_vif:
vif_ofport = execute_get_output('/usr/bin/ovs-vsctl', 'get',
'Interface', vif, 'ofport')
params = dict(VIF_NAME=vif,
MAC=data['mac'],
OF_PORT=vif_ofport)
ovs = OvsFlow(bridge, params)
if command == 'offline':
# I haven't found a way to clear only IPv4 or IPv6 rules.
ovs.clear_flows(vif_ofport)
if command == 'online':
if net_type in ('ipv4', 'all') and 'ips' in data:
for ip4 in data['ips']:
ovs.params.update({'IPV4_ADDR': ip4['ip']})
apply_ovs_ipv4_flows(ovs, bridge, params)
if net_type in ('ipv6', 'all') and 'ip6s' in data:
for ip6 in data['ip6s']:
link_local = str(netaddr.EUI(data['mac']).eui64()\
.ipv6_link_local())
ovs.params.update({'IPV6_LINK_LOCAL_ADDR': link_local})
ovs.params.update({'IPV6_GLOBAL_ADDR': ip6['ip']})
apply_ovs_ipv6_flows(ovs, bridge, params)
def apply_ovs_ipv4_flows(ovs, bridge, params):
# allow valid ARP outbound (both request / reply)
ovs.add("priority=3,in_port=%(OF_PORT)s,dl_src=%(MAC)s,arp,"
"arp_sha=%(MAC)s,nw_src=%(IPV4_ADDR)s,actions=normal")
ovs.add("priority=3,in_port=%(OF_PORT)s,dl_src=%(MAC)s,arp,"
"arp_sha=%(MAC)s,nw_src=0.0.0.0,actions=normal")
# allow valid IPv4 outbound
ovs.add("priority=3,in_port=%(OF_PORT)s,dl_src=%(MAC)s,ip,"
"nw_src=%(IPV4_ADDR)s,actions=normal")
def apply_ovs_ipv6_flows(ovs, bridge, params):
# allow valid IPv6 ND outbound (are both global and local IPs needed?)
# Neighbor Solicitation
ovs.add("priority=6,in_port=%(OF_PORT)s,dl_src=%(MAC)s,icmp6,"
"ipv6_src=%(IPV6_LINK_LOCAL_ADDR)s,icmp_type=135,nd_sll=%(MAC)s,"
"actions=normal")
ovs.add("priority=6,in_port=%(OF_PORT)s,dl_src=%(MAC)s,icmp6,"
"ipv6_src=%(IPV6_LINK_LOCAL_ADDR)s,icmp_type=135,actions=normal")
ovs.add("priority=6,in_port=%(OF_PORT)s,dl_src=%(MAC)s,icmp6,"
"ipv6_src=%(IPV6_GLOBAL_ADDR)s,icmp_type=135,nd_sll=%(MAC)s,"
"actions=normal")
ovs.add("priority=6,in_port=%(OF_PORT)s,dl_src=%(MAC)s,icmp6,"
"ipv6_src=%(IPV6_GLOBAL_ADDR)s,icmp_type=135,actions=normal")
# Neighbor Advertisement
ovs.add("priority=6,in_port=%(OF_PORT)s,dl_src=%(MAC)s,icmp6,"
"ipv6_src=%(IPV6_LINK_LOCAL_ADDR)s,icmp_type=136,"
"nd_target=%(IPV6_LINK_LOCAL_ADDR)s,actions=normal")
ovs.add("priority=6,in_port=%(OF_PORT)s,dl_src=%(MAC)s,icmp6,"
"ipv6_src=%(IPV6_LINK_LOCAL_ADDR)s,icmp_type=136,actions=normal")
ovs.add("priority=6,in_port=%(OF_PORT)s,dl_src=%(MAC)s,icmp6,"
"ipv6_src=%(IPV6_GLOBAL_ADDR)s,icmp_type=136,"
"nd_target=%(IPV6_GLOBAL_ADDR)s,actions=normal")
ovs.add("priority=6,in_port=%(OF_PORT)s,dl_src=%(MAC)s,icmp6,"
"ipv6_src=%(IPV6_GLOBAL_ADDR)s,icmp_type=136,actions=normal")
# drop all other neighbor discovery (req b/c we permit all icmp6 below)
ovs.add("priority=5,in_port=%(OF_PORT)s,icmp6,icmp_type=135,actions=drop")
ovs.add("priority=5,in_port=%(OF_PORT)s,icmp6,icmp_type=136,actions=drop")
# do not allow sending specifc ICMPv6 types
# Router Advertisement
ovs.add("priority=5,in_port=%(OF_PORT)s,icmp6,icmp_type=134,actions=drop")
# Redirect Gateway
ovs.add("priority=5,in_port=%(OF_PORT)s,icmp6,icmp_type=137,actions=drop")
# Mobile Prefix Solicitation
ovs.add("priority=5,in_port=%(OF_PORT)s,icmp6,icmp_type=146,actions=drop")
# Mobile Prefix Advertisement
ovs.add("priority=5,in_port=%(OF_PORT)s,icmp6,icmp_type=147,actions=drop")
# Multicast Router Advertisement
ovs.add("priority=5,in_port=%(OF_PORT)s,icmp6,icmp_type=151,actions=drop")
# Multicast Router Solicitation
ovs.add("priority=5,in_port=%(OF_PORT)s,icmp6,icmp_type=152,actions=drop")
# Multicast Router Termination
ovs.add("priority=5,in_port=%(OF_PORT)s,icmp6,icmp_type=153,actions=drop")
# allow valid IPv6 outbound, by type
ovs.add("priority=4,in_port=%(OF_PORT)s,dl_src=%(MAC)s,"
"ipv6_src=%(IPV6_GLOBAL_ADDR)s,icmp6,actions=normal")
ovs.add("priority=4,in_port=%(OF_PORT)s,dl_src=%(MAC)s,"
"ipv6_src=%(IPV6_LINK_LOCAL_ADDR)s,icmp6,actions=normal")
ovs.add("priority=4,in_port=%(OF_PORT)s,dl_src=%(MAC)s,"
"ipv6_src=%(IPV6_GLOBAL_ADDR)s,tcp6,actions=normal")
ovs.add("priority=4,in_port=%(OF_PORT)s,dl_src=%(MAC)s,"
"ipv6_src=%(IPV6_LINK_LOCAL_ADDR)s,tcp6,actions=normal")
ovs.add("priority=4,in_port=%(OF_PORT)s,dl_src=%(MAC)s,"
"ipv6_src=%(IPV6_GLOBAL_ADDR)s,udp6,actions=normal")
ovs.add("priority=4,in_port=%(OF_PORT)s,dl_src=%(MAC)s,"
"ipv6_src=%(IPV6_LINK_LOCAL_ADDR)s,udp6,actions=normal")
# all else will be dropped ...
if __name__ == "__main__":
if len(sys.argv) != 4:
print "usage: %s [online|offline] vif-domid-idx [ipv4|ipv6|all] " % \
os.path.basename(sys.argv[0])
sys.exit(1)
else:
command, vif_raw, net_type = sys.argv[1:4]
main(command, vif_raw, net_type)

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 OpenStack LLC.
# Copyright 2010-2011 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -29,15 +29,18 @@ import sys
import simplejson as json
from novalib import execute, execute_get_output
def main(dom_id, command, only_this_vif=None):
xsls = execute('/usr/bin/xenstore-ls',
'/local/domain/%s/vm-data/networking' % dom_id, True)
xsls = execute_get_output('/usr/bin/xenstore-ls',
'/local/domain/%s/vm-data/networking' % dom_id)
macs = [line.split("=")[0].strip() for line in xsls.splitlines()]
for mac in macs:
xsread = execute('/usr/bin/enstore-read',
'/local/domain/%s/vm-data/networking/%s' %
(dom_id, mac), True)
xsread = execute_get_output('/usr/bin/xenstore-read',
'/local/domain/%s/vm-data/networking/%s' %
(dom_id, mac))
data = json.loads(xsread)
for ip in data['ips']:
if data["label"] == "public":
@ -52,17 +55,6 @@ def main(dom_id, command, only_this_vif=None):
apply_iptables_rules(command, params)
def execute(*command, return_stdout=False):
devnull = open(os.devnull, 'w')
command = map(str, command)
proc = subprocess.Popen(command, close_fds=True,
stdout=subprocess.PIPE, stderr=devnull)
devnull.close()
if return_stdout:
return proc.stdout.read()
else:
return None
# A note about adding rules:
# Whenever we add any rule to iptables, arptables or ebtables we first
# delete the same rule to ensure the rule only exists once.
@ -113,8 +105,8 @@ def apply_ebtables_rules(command, params):
ebtables('-D', 'FORWARD', '-p', '0806', '-o', params['VIF'],
'--arp-ip-dst', params['IP'],
'-j', 'ACCEPT')
ebtables('-D', 'FORWARD', '-p', '0800', '-o',
params['VIF'], '--ip-dst', params['IP'],
ebtables('-D', 'FORWARD', '-p', '0800', '-o', params['VIF'],
'--ip-dst', params['IP'],
'-j', 'ACCEPT')
if command == 'online':
ebtables('-A', 'FORWARD', '-p', '0806',