spaces after comma
[nodemanager.git] / plugins / ipv6.py
1 # -*- python-indent: 4 -*-
2
3 """
4 Description: IPv6 Support and Management to Slices
5 ipv6 nodemanager plugin
6 Version: 0.8
7 Author: Guilherme Sperb Machado <gsm@machados.org>
8
9 Requirements:
10 * The 'sliversipv6prefix' tag must have this format:
11   ipv6_address/prefix -- e.g., 2002:1000::1/64
12 * The prefix specified on 'sliversipv6prefix' tag must be at least 64
13   It should vary between 1 and 64, since it is the minimum amount of bits to
14   have native IPv6 auto-configuration.
15 * The ipv6_address in 'sliversipv6prefix' tag value can be any valid IPv6 address.
16   E.g., 2002:1000:: or 2002:1000::1
17 * It is the node manager/admin responsibility to properly set the IPv6 routing,
18   since slivers should receive/send any kind of traffic.
19 """
20
21 import logger
22 import os
23 import socket
24 import re
25
26 import tools
27 import uuid
28 from xml.dom.minidom import parseString
29
30 # TODO: is there anything better to do if the "libvirt", "sliver_libvirt",
31 # and are not in place in the VS distro?
32 try:
33     import libvirt
34     from sliver_libvirt import Sliver_Libvirt
35 except:
36     logger.log("Could not import 'sliver_lxc' or 'libvirt'.")
37
38 priority=4
39
40 radvd_conf_file = '/etc/radvd.conf'
41 sliversipv6prefixtag = 'sliversipv6prefix'
42
43 def start():
44     logger.log("ipv6: plugin starting up...")
45
46 def build_libvirt_default_net_config(dom):
47
48     # create the <network> element
49     networkElem = dom.createElement("network")
50     # create <name> element
51     nameElem = dom.createElement("name")
52     textName = dom.createTextNode("default")
53     nameElem.appendChild(textName)
54     # create <uuid> element
55     uuidElem = dom.createElement("uuid")
56     textUUID = dom.createTextNode(str(uuid.uuid1()))
57     uuidElem.appendChild(textUUID)
58     # create <forward> element
59     forwardElem = dom.createElement("forward")
60     forwardElem.setAttribute("mode", "nat")
61     # create <nat> element
62     natElem = dom.createElement("nat")
63     # create <port> element
64     portElem = dom.createElement("port")
65     portElem.setAttribute("end", "65535")
66     portElem.setAttribute("start", "1024")
67     # create the ipv4 <ip> element
68     ipElem0 = dom.createElement("ip")
69     ipElem0.setAttribute("address", "192.168.122.1")
70     ipElem0.setAttribute("netmask", "255.255.255.0")
71     # create the <dhcp> element
72     dhcpElem = dom.createElement("dhcp")
73     # create the <range> element
74     rangeElem = dom.createElement("range")
75     rangeElem.setAttribute("end", "192.168.122.254")
76     rangeElem.setAttribute("start", "192.168.122.2")
77     # create the <bridge> element
78     bridgeElem = dom.createElement("bridge")
79     bridgeElem.setAttribute("delay", "0")
80     bridgeElem.setAttribute("name", "virbr0")
81     bridgeElem.setAttribute("stp", "on")
82
83     # build the whole thing
84     natElem.appendChild(portElem)
85     forwardElem.appendChild(natElem)
86
87     dhcpElem.appendChild(rangeElem)
88     ipElem0.appendChild(dhcpElem)
89     networkElem.appendChild(nameElem)
90     networkElem.appendChild(uuidElem)
91     networkElem.appendChild(forwardElem)
92     networkElem.appendChild(bridgeElem)
93     networkElem.appendChild(ipElem0)
94     return networkElem
95
96 def check_for_ipv6(defaultNetworkConfig):
97     netnodes = defaultNetworkConfig.getElementsByTagName('network')
98     hasIPv6 = False
99     for netnode in netnodes:
100         ips = netnode.getElementsByTagName('ip')
101         for ip in ips:
102             if ip.getAttribute('family')=='ipv6':
103                 logger.log("ipv6: IPv6 address/prefix already set for slivers! %s/%s" % \
104                            (ip.getAttribute('address'), ip.getAttribute('prefix')) )
105                 hasIPv6 = True
106     return hasIPv6
107
108
109 def add_ipv6(defaultNetworkConfig, ipv6addr, prefix):
110
111     netnodes = defaultNetworkConfig.getElementsByTagName('network')
112     for netnode in netnodes:
113         # create the ipv6 <ip> element 1
114         ipElem1 = defaultNetworkConfig.createElement("ip")
115         ipElem1.setAttribute("family", "ipv6")
116         ipElem1.setAttribute("address", ipv6addr)
117         ipElem1.setAttribute("prefix", prefix)
118         # create the ipv6 <ip> element 2
119         # it's ugly, I know, but we need a link-local address on the interface!
120         ipElem2 = defaultNetworkConfig.createElement("ip")
121         ipElem2.setAttribute("family", "ipv6")
122         ipElem2.setAttribute("address", "fe80:1234::1")
123         ipElem2.setAttribute("prefix", "64")
124         # adding to the 'defaultNetworkConfig'
125         netnode.appendChild(ipElem1)
126         netnode.appendChild(ipElem2)
127     return defaultNetworkConfig
128
129 def change_ipv6(dom, ipv6addr, prefix):
130     ips = dom.getElementsByTagName('ip')
131     for ip in ips:
132         if ip.getAttribute("family")=='ipv6' and not(re.match(r'fe80(.*)', ip.getAttribute("address"), re.I)):
133             ip.setAttribute("address", ipv6addr)
134             ip.setAttribute("prefix", prefix)
135     return dom
136
137
138 def remove_ipv6(dom):
139     networks = dom.getElementsByTagName('network')
140     for network in networks:
141         ips = network.getElementsByTagName('ip')
142         for ip in ips:
143             if ip.getAttribute("family")=='ipv6':
144                 network.removeChild(ip)
145     return dom
146
147
148 def check_if_ipv6_is_different(dom, ipv6addr, prefix):
149     netnodes = dom.getElementsByTagName('network')
150     for netnode in netnodes:
151         ips = netnode.getElementsByTagName('ip')
152         for ip in ips:
153             if ip.getAttribute('family')=='ipv6' and \
154                    not ( re.match(r'fe80(.*)', ip.getAttribute("address"), re.I) ) and \
155                    (ip.getAttribute('address')!=ipv6addr or ip.getAttribute('prefix')!=prefix) :
156                 logger.log("ipv6: IPv6 address or prefix are different. Change detected!")
157                 return True
158     return False
159
160
161 def set_autostart(network):
162     try:
163         network.setAutostart(1)
164     except:
165         logger.log("ipv6: network could not set to autostart")
166
167
168 def set_up(networkLibvirt, connLibvirt, networkElem, ipv6addr, prefix):
169     newXml = networkElem.toxml()
170     #logger.log(networkElem.toxml())
171     #ret = dir(conn)
172     #for method in ret:
173     #    logger.log(repr(method))
174     networkLibvirt.undefine()
175     networkLibvirt.destroy()
176     connLibvirt.networkCreateXML(newXml)
177     networkDefault = connLibvirt.networkDefineXML(newXml)
178     set_autostart(networkDefault)
179     commandForwarding = ['sysctl', '-w', 'net.ipv6.conf.all.forwarding=1']
180     logger.log_call(commandForwarding, timeout=15*60)
181     configRadvd = """
182 interface virbr0
183 {
184         AdvSendAdvert on;
185         MinRtrAdvInterval 30;
186         MaxRtrAdvInterval 100;
187         prefix %(ipv6addr)s/%(prefix)s
188         {
189                 AdvOnLink on;
190                 AdvAutonomous on;
191                 AdvRouterAddr off;
192         };
193
194 };
195 """ % locals()
196     with open(radvd_conf_file, 'w') as f:
197         f.write(configRadvd)
198     kill_radvd()
199     start_radvd()
200     logger.log("ipv6: set up process finalized -- enabled IPv6 address to the slivers!")
201
202 def clean_up(networkLibvirt, connLibvirt, networkElem):
203     dom = remove_ipv6(networkElem)
204     newXml = dom.toxml()
205     networkLibvirt.undefine()
206     networkLibvirt.destroy()
207     # TODO: set autostart for the network
208     connLibvirt.networkCreateXML(newXml)
209     networkDefault = connLibvirt.networkDefineXML(newXml)
210     set_autostart(networkDefault)
211     kill_radvd()
212     logger.log("ipv6: cleanup process finalized. The IPv6 support on the slivers was removed.")
213
214 def kill_radvd():
215     command_kill_radvd = ['killall', 'radvd']
216     logger.log_call(command_kill_radvd, timeout=15*60)
217
218 def start_radvd():
219     commandRadvd = ['radvd']
220     logger.log_call(commandRadvd, timeout=15*60)
221
222 def GetSlivers(data, config, plc):
223
224     type = 'sliver.LXC'
225     virt=tools.get_node_virt()
226     if virt!='lxc':
227         return
228
229     interfaces = data['interfaces']
230     logger.log(repr(interfaces))
231     for interface in interfaces:
232         #logger.log('ipv6: get interface: %r'%(interface))
233         if 'interface_tag_ids' in interface:
234             interface_tag_ids = "interface_tag_ids"
235             interface_tag_id = "interface_tag_id"
236             settings = plc.GetInterfaceTags({interface_tag_id:interface[interface_tag_ids]})
237             is_slivers_ipv6_prefix_set = False
238             for setting in settings:
239                 if setting['tagname']==sliversipv6prefixtag:
240                     ipv6addrprefix = setting['value'].split('/', 1)
241                     ipv6addr = ipv6addrprefix[0]
242                     valid_prefix = False
243                     #logger.log("ipv6: len(ipv6addrprefix)=%s" % (len(ipv6addrprefix)) )
244                     if len(ipv6addrprefix)>1:
245                         prefix = ipv6addrprefix[1]
246                         #logger.log("ipv6: prefix=%s" % (prefix) )
247                         if int(prefix)>0 and int(prefix)<=64:
248                             valid_prefix = True
249                         else:
250                             valid_prefix = False
251                     else:
252                         valid_prefix = False
253                     #logger.log("ipv6: '%s'=%s" % (sliversipv6prefixtag, ipv6addr) )
254                     valid_ipv6 = tools.is_valid_ipv6(ipv6addr)
255                     if not(valid_ipv6):
256                         logger.log("ipv6: the 'sliversipv6prefix' tag presented a non-valid IPv6 address!")
257                     elif not(valid_prefix):
258                             logger.log("ipv6: the '%s' tag does not present a valid prefix (e.g., '/64', '/58')!" % \
259                                        (sliversipv6prefixtag))
260                     else:
261                         # connecting to the libvirtd
262                         connLibvirt = Sliver_Libvirt.getConnection(type)
263                         list = connLibvirt.listAllNetworks()
264                         for networkLibvirt in list:
265                             xmldesc = networkLibvirt.XMLDesc()
266                             dom = parseString(xmldesc)
267                             has_ipv6 = check_for_ipv6(dom)
268                             if has_ipv6:
269                                 # let's first check if the IPv6 is different or is it the same...
270                                 is_different = check_if_ipv6_is_different(dom, ipv6addr, prefix)
271                                 if is_different:
272                                     logger.log("ipv6: tag 'sliversipv6prefix' was modified! " +
273                                            "Updating configuration with the new one...")
274                                     network_elem = change_ipv6(dom, ipv6addr, prefix)
275                                     set_up(networkLibvirt, connLibvirt, network_elem, ipv6addr, prefix)
276                                     logger.log("ipv6: trying to reboot the slivers...")
277                                     tools.reboot_slivers()
278                             else:
279                                 logger.log("ipv6: starting to redefine the virtual network...")
280                                 #network_elem = buildLibvirtDefaultNetConfig(dom, ipv6addr, prefix)
281                                 network_elem = add_ipv6(dom, ipv6addr, prefix)
282                                 set_up(networkLibvirt, connLibvirt, network_elem, ipv6addr, prefix)
283                                 logger.log("ipv6: trying to reboot the slivers...")
284                                 tools.reboot_slivers()
285                         is_slivers_ipv6_prefix_set = True
286             if not(is_slivers_ipv6_prefix_set):
287                 # connecting to the libvirtd
288                 connLibvirt = Sliver_Libvirt.getConnection(type)
289                 list = connLibvirt.listAllNetworks()
290                 for networkLibvirt in list:
291                     xmldesc = networkLibvirt.XMLDesc()
292                     dom = parseString(xmldesc)
293                     if check_for_ipv6(dom):
294                         clean_up(networkLibvirt, connLibvirt, dom)
295                         logger.log("ipv6: trying to reboot the slivers...")
296                         tools.reboot_slivers()
297
298     logger.log("ipv6: all done!")