19 # @author: Dan Wendlandt, Nicira, Inc |
19 # @author: Dan Wendlandt, Nicira, Inc |
20 # @author: Girish Moodalbail, Oracle, Inc. |
20 # @author: Girish Moodalbail, Oracle, Inc. |
21 # |
21 # |
22 |
22 |
23 """ |
23 """ |
24 Based off generic l3_agent (quantum/agent/l3_agent) code |
24 Based off generic l3_agent (neutron/agent/l3_agent) code |
25 """ |
25 """ |
26 |
26 |
|
27 import netaddr |
|
28 |
27 from oslo.config import cfg |
29 from oslo.config import cfg |
28 |
30 |
29 from quantum.agent import l3_agent |
31 from neutron.agent import l3_agent |
30 from quantum.agent.solaris import interface |
32 from neutron.agent.linux import utils |
31 from quantum.agent.solaris import ipfilters_manager |
33 from neutron.agent.solaris import interface |
32 from quantum.agent.solaris import net_lib |
34 from neutron.agent.solaris import ipfilters_manager |
33 from quantum.common import constants as l3_constants |
35 from neutron.agent.solaris import net_lib |
34 from quantum.openstack.common import log as logging |
36 from neutron.common import constants as l3_constants |
|
37 from neutron.openstack.common import log as logging |
35 |
38 |
36 |
39 |
37 LOG = logging.getLogger(__name__) |
40 LOG = logging.getLogger(__name__) |
38 INTERNAL_DEV_PREFIX = 'l3i' |
41 INTERNAL_DEV_PREFIX = 'l3i' |
39 EXTERNAL_DEV_PREFIX = 'l3e' |
42 EXTERNAL_DEV_PREFIX = 'l3e' |
|
43 FLOATING_IP_CIDR_SUFFIX = '/32' |
40 |
44 |
41 |
45 |
42 class RouterInfo(object): |
46 class RouterInfo(object): |
43 |
47 |
44 def __init__(self, router_id, root_helper, use_namespaces, router): |
48 def __init__(self, router_id, root_helper, use_namespaces, router): |
45 self.router_id = router_id |
49 self.router_id = router_id |
46 self.ex_gw_port = None |
50 self.ex_gw_port = None |
|
51 self._snat_enabled = None |
|
52 self._snat_action = None |
47 self.internal_ports = [] |
53 self.internal_ports = [] |
48 self.floating_ips = [] |
54 # We do not need either root_helper or namespace, so set them to None |
|
55 self.root_helper = None |
|
56 self.use_namespaces = None |
|
57 # Invoke the setter for establishing initial SNAT action |
49 self.router = router |
58 self.router = router |
50 self.ipfilters_manager = ipfilters_manager.IpfiltersManager() |
59 self.ipfilters_manager = ipfilters_manager.IPfiltersManager() |
51 self.routes = [] |
60 self.routes = [] |
52 |
61 |
|
62 @property |
|
63 def router(self): |
|
64 return self._router |
|
65 |
|
66 @router.setter |
|
67 def router(self, value): |
|
68 self._router = value |
|
69 if not self._router: |
|
70 return |
|
71 # enable_snat by default if it wasn't specified by plugin |
|
72 self._snat_enabled = self._router.get('enable_snat', True) |
|
73 # Set a SNAT action for the router |
|
74 if self._router.get('gw_port'): |
|
75 self._snat_action = ('add_rules' if self._snat_enabled |
|
76 else 'remove_rules') |
|
77 elif self.ex_gw_port: |
|
78 # Gateway port was removed, remove rules |
|
79 self._snat_action = 'remove_rules' |
|
80 |
|
81 def ns_name(self): |
|
82 pass |
|
83 |
|
84 def perform_snat_action(self, snat_callback, *args): |
|
85 # Process SNAT rules for attached subnets |
|
86 if self._snat_action: |
|
87 snat_callback(self, self._router.get('gw_port'), |
|
88 *args, action=self._snat_action) |
|
89 self._snat_action = None |
|
90 |
53 |
91 |
54 class EVSL3NATAgent(l3_agent.L3NATAgent): |
92 class EVSL3NATAgent(l3_agent.L3NATAgent): |
55 |
93 |
56 RouterInfo = RouterInfo |
94 RouterInfo = RouterInfo |
57 |
95 |
58 OPTS = [ |
96 OPTS = [ |
59 cfg.StrOpt('external_network_bridge', default='', |
|
60 help=_("Name of bridge used for external network " |
|
61 "traffic.")), |
|
62 cfg.StrOpt('interface_driver', |
|
63 help=_("The driver used to manage the virtual " |
|
64 "interface.")), |
|
65 cfg.BoolOpt('use_namespaces', default=False, |
|
66 help=_("Allow overlapping IP.")), |
|
67 cfg.StrOpt('router_id', |
|
68 help=_("If namespaces is disabled, the l3 agent can only" |
|
69 " configure a router that has the matching router " |
|
70 "ID.")), |
|
71 cfg.BoolOpt('handle_internal_only_routers', default=True, |
|
72 help=_("Agent should implement routers with no gateway")), |
|
73 cfg.StrOpt('gateway_external_network_id', default='', |
|
74 help=_("UUID of external network for routers implemented " |
|
75 "by the agents.")), |
|
76 cfg.StrOpt('external_network_datalink', default='net0', |
97 cfg.StrOpt('external_network_datalink', default='net0', |
77 help=_("Name of the datalink that connects to " |
98 help=_("Name of the datalink that connects to " |
78 "an external network.")), |
99 "an external network.")), |
79 cfg.BoolOpt('allow_forwarding_between_networks', default=False, |
100 cfg.BoolOpt('allow_forwarding_between_networks', default=False, |
80 help=_("Allow forwarding of packets between tenant's " |
101 help=_("Allow forwarding of packets between tenant's " |
82 ] |
103 ] |
83 |
104 |
84 def __init__(self, host, conf=None): |
105 def __init__(self, host, conf=None): |
85 cfg.CONF.register_opts(self.OPTS) |
106 cfg.CONF.register_opts(self.OPTS) |
86 cfg.CONF.register_opts(interface.OPTS) |
107 cfg.CONF.register_opts(interface.OPTS) |
87 if not cfg.CONF.router_id: |
|
88 raise SystemExit(_("router_id option needs to be set")) |
|
89 |
|
90 super(EVSL3NATAgent, self).__init__(host=host, conf=conf) |
108 super(EVSL3NATAgent, self).__init__(host=host, conf=conf) |
91 |
109 |
92 def _router_added(self, router_id, router): |
110 def _router_added(self, router_id, router): |
93 ri = RouterInfo(router_id, self.root_helper, |
111 ri = RouterInfo(router_id, self.root_helper, |
94 self.conf.use_namespaces, router) |
112 self.conf.use_namespaces, router) |
99 ri.router['gw_port'] = None |
117 ri.router['gw_port'] = None |
100 ri.router[l3_constants.INTERFACE_KEY] = [] |
118 ri.router[l3_constants.INTERFACE_KEY] = [] |
101 ri.router[l3_constants.FLOATINGIP_KEY] = [] |
119 ri.router[l3_constants.FLOATINGIP_KEY] = [] |
102 self.process_router(ri) |
120 self.process_router(ri) |
103 del self.router_info[router_id] |
121 del self.router_info[router_id] |
|
122 |
|
123 def process_router(self, ri): |
|
124 ex_gw_port = self._get_ex_gw_port(ri) |
|
125 internal_ports = ri.router.get(l3_constants.INTERFACE_KEY, []) |
|
126 existing_port_ids = set([p['id'] for p in ri.internal_ports]) |
|
127 current_port_ids = set([p['id'] for p in internal_ports |
|
128 if p['admin_state_up']]) |
|
129 new_ports = [p for p in internal_ports if |
|
130 p['id'] in current_port_ids and |
|
131 p['id'] not in existing_port_ids] |
|
132 old_ports = [p for p in ri.internal_ports if |
|
133 p['id'] not in current_port_ids] |
|
134 for p in new_ports: |
|
135 self._set_subnet_info(p) |
|
136 ri.internal_ports.append(p) |
|
137 self.internal_network_added(ri, p) |
|
138 |
|
139 for p in old_ports: |
|
140 ri.internal_ports.remove(p) |
|
141 self.internal_network_removed(ri, p) |
|
142 |
|
143 internal_cidrs = [p['ip_cidr'] for p in ri.internal_ports] |
|
144 # TODO(salv-orlando): RouterInfo would be a better place for |
|
145 # this logic too |
|
146 ex_gw_port_id = (ex_gw_port and ex_gw_port['id'] or |
|
147 ri.ex_gw_port and ri.ex_gw_port['id']) |
|
148 |
|
149 interface_name = None |
|
150 if ex_gw_port_id: |
|
151 interface_name = self.get_external_device_name(ex_gw_port_id) |
|
152 if ex_gw_port and not ri.ex_gw_port: |
|
153 self._set_subnet_info(ex_gw_port) |
|
154 self.external_gateway_added(ri, ex_gw_port, |
|
155 interface_name, internal_cidrs) |
|
156 elif not ex_gw_port and ri.ex_gw_port: |
|
157 self.external_gateway_removed(ri, ri.ex_gw_port, |
|
158 interface_name, internal_cidrs) |
|
159 |
|
160 # We don't need this since our IPnat rules are bi-directional |
|
161 # Process SNAT rules for external gateway |
|
162 # ri.perform_snat_action(self._handle_router_snat_rules, |
|
163 # internal_cidrs, interface_name) |
|
164 |
|
165 # Process DNAT rules for floating IPs |
|
166 if ex_gw_port: |
|
167 self.process_router_floating_ips(ri, ex_gw_port) |
|
168 |
|
169 ri.ex_gw_port = ex_gw_port |
|
170 ri.enable_snat = ri.router.get('enable_snat') |
|
171 self.routes_updated(ri) |
|
172 |
|
173 def process_router_floating_ips(self, ri, ex_gw_port): |
|
174 """Configure the router's floating IPs |
|
175 Configures floating ips using ipnat(1m) on the router's gateway device. |
|
176 |
|
177 Cleans up floating ips that should not longer be configured. |
|
178 """ |
|
179 ifname = self.get_external_device_name(ex_gw_port['id']) |
|
180 ipintf = net_lib.IPInterface(ifname) |
|
181 ipaddr_list = ipintf.ipaddr_list()['static'] |
|
182 |
|
183 # Clear out all ipnat rules for floating ips |
|
184 ri.ipfilters_manager.remove_nat_rules(ri.ipfilters_manager.ipv4['nat']) |
|
185 |
|
186 existing_cidrs = set([addr for addr in ipaddr_list]) |
|
187 new_cidrs = set() |
|
188 |
|
189 # Loop once to ensure that floating ips are configured. |
|
190 for fip in ri.router.get(l3_constants.FLOATINGIP_KEY, []): |
|
191 fip_ip = fip['floating_ip_address'] |
|
192 fip_cidr = str(fip_ip) + FLOATING_IP_CIDR_SUFFIX |
|
193 |
|
194 new_cidrs.add(fip_cidr) |
|
195 |
|
196 if fip_cidr not in existing_cidrs: |
|
197 ipintf.create_address(fip_cidr) |
|
198 |
|
199 # Rebuild iptables rules for the floating ip. |
|
200 fixed_cidr = str(fip['fixed_ip_address']) + '/32' |
|
201 nat_rules = ['bimap %s %s -> %s' % (ifname, fixed_cidr, fip_cidr)] |
|
202 ri.ipfilters_manager.add_nat_rules(nat_rules) |
|
203 |
|
204 # Clean up addresses that no longer belong on the gateway interface. |
|
205 for ip_cidr in existing_cidrs - new_cidrs: |
|
206 if ip_cidr.endswith(FLOATING_IP_CIDR_SUFFIX): |
|
207 ipintf.delete_address(ip_cidr) |
104 |
208 |
105 def get_internal_device_name(self, port_id): |
209 def get_internal_device_name(self, port_id): |
106 # Because of the way how dnsmasq works on Solaris, the length |
210 # Because of the way how dnsmasq works on Solaris, the length |
107 # of datalink name cannot exceed 16 (includes terminating nul |
211 # of datalink name cannot exceed 16 (includes terminating nul |
108 # character). So, the linkname can only have 15 characters and |
212 # character). So, the linkname can only have 15 characters and |
111 dname = (INTERNAL_DEV_PREFIX + port_id)[:13] |
215 dname = (INTERNAL_DEV_PREFIX + port_id)[:13] |
112 dname += '_0' |
216 dname += '_0' |
113 return dname.replace('-', '_') |
217 return dname.replace('-', '_') |
114 |
218 |
115 def get_external_device_name(self, port_id): |
219 def get_external_device_name(self, port_id): |
|
220 # please see the comment above |
116 dname = (EXTERNAL_DEV_PREFIX + port_id)[:13] |
221 dname = (EXTERNAL_DEV_PREFIX + port_id)[:13] |
117 dname += '_0' |
222 dname += '_0' |
118 return dname.replace('-', '_') |
223 return dname.replace('-', '_') |
119 |
224 |
120 def external_gateway_added(self, ri, ex_gw_port, internal_cidrs): |
225 def external_gateway_added(self, ri, ex_gw_port, |
121 pass |
226 external_dlname, internal_cidrs): |
122 |
227 |
123 def external_gateway_removed(self, ri, ex_gw_port, internal_cidrs): |
228 if not net_lib.Datalink.datalink_exists(external_dlname): |
124 pass |
229 dl = net_lib.Datalink(external_dlname) |
|
230 # need to determine the VLAN ID for the VNIC |
|
231 evsname = ex_gw_port['network_id'] |
|
232 tenantname = ex_gw_port['tenant_id'] |
|
233 cmd = ['/usr/sbin/evsadm', 'show-evs', '-co', 'vid', |
|
234 '-f', 'tenant=%s' % tenantname, evsname] |
|
235 try: |
|
236 stdout = utils.execute(cmd) |
|
237 except Exception as err: |
|
238 LOG.error(_("Failed to retrieve the VLAN ID associated " |
|
239 "with the external network, and it is required " |
|
240 "to create external gateway port: %s") % err) |
|
241 return |
|
242 vid = stdout.splitlines()[0].strip() |
|
243 if vid == "": |
|
244 LOG.error(_("External Network does not has a VLAN ID " |
|
245 "associated with it, and it is required to " |
|
246 "create external gateway port")) |
|
247 return |
|
248 mac_address = ex_gw_port['mac_address'] |
|
249 dl.create_vnic(self.conf.external_network_datalink, |
|
250 mac_address=mac_address, vid=vid) |
|
251 self.driver.init_l3(external_dlname, [ex_gw_port['ip_cidr']]) |
|
252 |
|
253 # TODO(gmoodalb): wrap route(1m) command within a class in net_lib.py |
|
254 gw_ip = ex_gw_port['subnet']['gateway_ip'] |
|
255 if gw_ip: |
|
256 cmd = ['/usr/bin/pfexec', '/usr/sbin/route', 'add', 'default', |
|
257 gw_ip] |
|
258 utils.execute(cmd, check_exit_code=False) |
|
259 |
|
260 def external_gateway_removed(self, ri, ex_gw_port, |
|
261 external_dlname, internal_cidrs): |
|
262 |
|
263 if net_lib.Datalink.datalink_exists(external_dlname): |
|
264 self.driver.fini_l3(external_dlname) |
|
265 self.driver.unplug(external_dlname) |
|
266 gw_ip = ex_gw_port['subnet']['gateway_ip'] |
|
267 if gw_ip: |
|
268 cmd = ['/usr/bin/pfexec', '/usr/sbin/route', 'delete', 'default', |
|
269 gw_ip] |
|
270 utils.execute(cmd, check_exit_code=False) |
125 |
271 |
126 def _get_ippool_name(self, mac_address): |
272 def _get_ippool_name(self, mac_address): |
127 # generate a unique-name for ippool(1m) from that last 3 |
273 # generate a unique-name for ippool(1m) from that last 3 |
128 # bytes of mac-address |
274 # bytes of mac-address |
129 mac_suffix = mac_address.split(':')[3:] |
275 mac_suffix = mac_address.split(':')[3:] |
130 return int("".join(mac_suffix), 16) |
276 return int("".join(mac_suffix), 16) |
131 |
277 |
132 def internal_network_added(self, ri, ex_gw_port, port): |
278 def internal_network_added(self, ri, port): |
133 |
279 |
134 internal_dlname = self.get_internal_device_name(port['id']) |
280 internal_dlname = self.get_internal_device_name(port['id']) |
135 if not net_lib.Datalink.datalink_exists(internal_dlname): |
281 if not net_lib.Datalink.datalink_exists(internal_dlname): |
136 self.driver.plug(port['tenant_id'], port['network_id'], port['id'], |
282 self.driver.plug(port['tenant_id'], port['network_id'], port['id'], |
137 internal_dlname) |
283 internal_dlname) |
160 # now setup the IPF rule |
306 # now setup the IPF rule |
161 rules = ['block in quick on %s from %s to pool/%d' % |
307 rules = ['block in quick on %s from %s to pool/%d' % |
162 (internal_dlname, subnet_cidr, new_ippool_name)] |
308 (internal_dlname, subnet_cidr, new_ippool_name)] |
163 ri.ipfilters_manager.add_ipf_rules(rules) |
309 ri.ipfilters_manager.add_ipf_rules(rules) |
164 |
310 |
165 def internal_network_removed(self, ri, ex_gw_port, port): |
311 def internal_network_removed(self, ri, port): |
166 internal_dlname = self.get_internal_device_name(port['id']) |
312 internal_dlname = self.get_internal_device_name(port['id']) |
167 if net_lib.Datalink.datalink_exists(internal_dlname): |
313 if net_lib.Datalink.datalink_exists(internal_dlname): |
168 self.driver.fini_l3(internal_dlname) |
314 self.driver.fini_l3(internal_dlname) |
169 self.driver.unplug(internal_dlname) |
315 self.driver.unplug(internal_dlname) |
170 |
316 |
175 ri.ipfilters_manager.remove_ipf_rules(rules) |
321 ri.ipfilters_manager.remove_ipf_rules(rules) |
176 # remove the ippool |
322 # remove the ippool |
177 ri.ipfilters_manager.remove_ippool(ippool_name, None) |
323 ri.ipfilters_manager.remove_ippool(ippool_name, None) |
178 for internal_port in ri.internal_ports: |
324 for internal_port in ri.internal_ports: |
179 if (self.conf.allow_forwarding_between_networks and |
325 if (self.conf.allow_forwarding_between_networks and |
180 internal_port['tenant_id'] == port['tenant_id']): |
326 internal_port['tenant_id'] == port['tenant_id']): |
181 continue |
327 continue |
182 ippool_name = \ |
328 ippool_name = \ |
183 self._get_ippool_name(internal_port['mac_address']) |
329 self._get_ippool_name(internal_port['mac_address']) |
184 ri.ipfilters_manager.remove_ippool(ippool_name, |
330 subnet_cidr = internal_port['subnet']['cidr'] |
185 internal_port['subnet']['cidr']) |
331 ri.ipfilters_manager.remove_ippool(ippool_name, [subnet_cidr]) |
186 |
332 |
187 def floating_ip_added(self, ri, ex_gw_port, floating_ip, fixed_ip): |
333 def routers_updated(self, context, routers): |
188 floating_ipcidr = str(floating_ip) + '/32' |
334 super(EVSL3NATAgent, self).routers_updated(context, routers) |
189 fixed_ipcidr = str(fixed_ip) + '/32' |
335 if routers: |
190 #ifname = self.get_external_device_name(ex_gw_port['id']) |
336 # If router's interface was removed, then the VNIC associated |
191 ifname = self.conf.external_network_datalink |
337 # with that interface must be deleted immediately. The EVS |
192 ipif = net_lib.IPInterface(ifname) |
338 # plugin can delete the virtual port iff the VNIC associated |
193 ipif.create_address(floating_ipcidr) |
339 # with that virtual port is deleted first. |
194 |
340 self._rpc_loop() |
195 nat_rules = ['bimap %s %s -> %s' % |
|
196 (ifname, fixed_ipcidr, floating_ipcidr)] |
|
197 ri.ipfilters_manager.add_nat_rules(nat_rules) |
|
198 |
|
199 def floating_ip_removed(self, ri, ex_gw_port, floating_ip, fixed_ip): |
|
200 floating_ipcidr = str(floating_ip) + '/32' |
|
201 fixed_ipcidr = str(fixed_ip) + '/32' |
|
202 ifname = self.conf.external_network_datalink |
|
203 ipif = net_lib.IPInterface(ifname) |
|
204 ipif.delete_address(floating_ipcidr) |
|
205 |
|
206 nat_rules = ['bimap %s %s -> %s' % |
|
207 (ifname, fixed_ipcidr, floating_ipcidr)] |
|
208 ri.ipfilters_manager.remove_nat_rules(nat_rules) |
|
209 |
341 |
210 def routes_updated(self, ri): |
342 def routes_updated(self, ri): |
211 pass |
343 pass |