blob: d6b74927f9cc8c183bade3337a8f6041033c6ffc [file] [log] [blame]
sangho6a9ff0d2017-03-27 11:23:37 +09001/*
2* Copyright 2017-present Open Networking Laboratory
3*
4* Licensed under the Apache License, Version 2.0 (the "License");
5* you may not use this file except in compliance with the License.
6* You may obtain a copy of the License at
7*
8* http://www.apache.org/licenses/LICENSE-2.0
9*
10* Unless required by applicable law or agreed to in writing, software
11* distributed under the License is distributed on an "AS IS" BASIS,
12* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13* See the License for the specific language governing permissions and
14* limitations under the License.
15*/
16
17package org.onosproject.openstacknetworking.impl;
18
19import com.google.common.base.Strings;
20import org.apache.felix.scr.annotations.Activate;
21import org.apache.felix.scr.annotations.Component;
22import org.apache.felix.scr.annotations.Deactivate;
23import org.apache.felix.scr.annotations.Reference;
24import org.apache.felix.scr.annotations.ReferenceCardinality;
25import org.onlab.packet.Ethernet;
26import org.onlab.packet.IPv4;
27import org.onlab.packet.Ip4Address;
28import org.onlab.packet.Ip4Prefix;
29import org.onlab.packet.IpPrefix;
30import org.onlab.packet.TpPort;
31import org.onosproject.core.ApplicationId;
32import org.onosproject.core.CoreService;
33import org.onosproject.mastership.MastershipService;
34import org.onosproject.net.flow.DefaultTrafficSelector;
35import org.onosproject.net.flow.DefaultTrafficTreatment;
36import org.onosproject.net.flow.TrafficSelector;
37import org.onosproject.net.flowobjective.DefaultForwardingObjective;
38import org.onosproject.net.flowobjective.FlowObjectiveService;
39import org.onosproject.net.flowobjective.ForwardingObjective;
40import org.onosproject.openstacknetworking.api.InstancePort;
41import org.onosproject.openstacknetworking.api.InstancePortEvent;
42import org.onosproject.openstacknetworking.api.InstancePortListener;
43import org.onosproject.openstacknetworking.api.InstancePortService;
44import org.onosproject.openstacknetworking.api.OpenstackNetworkEvent;
45import org.onosproject.openstacknetworking.api.OpenstackNetworkListener;
46import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
47import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupEvent;
48import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupListener;
49import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupService;
50import org.openstack4j.model.network.Port;
51import org.openstack4j.model.network.SecurityGroup;
52import org.openstack4j.model.network.SecurityGroupRule;
53import org.openstack4j.openstack.networking.domain.NeutronSecurityGroupRule;
54import org.slf4j.Logger;
55
sangho6a9ff0d2017-03-27 11:23:37 +090056import java.util.Collections;
57import java.util.Objects;
58import java.util.Set;
59import java.util.concurrent.ExecutorService;
60import java.util.stream.Collectors;
61
62import static java.util.concurrent.Executors.newSingleThreadExecutor;
63import static org.onlab.util.Tools.groupedThreads;
64import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
65import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_ACL_RULE;
66import static org.slf4j.LoggerFactory.getLogger;
67
68/**
69 * Populates flow rules to handle OpenStack SecurityGroups.
70 */
71@Component(immediate = true)
72public class OpenstackSecurityGroupHandler {
73
74 private final Logger log = getLogger(getClass());
75
76 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
77 protected CoreService coreService;
78
79 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
80 protected InstancePortService instancePortService;
81
82 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
83 protected MastershipService mastershipService;
84
85 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Hyunsun Moonae51e732017-04-25 17:46:21 +090086 protected OpenstackNetworkService osNetService;
sangho6a9ff0d2017-03-27 11:23:37 +090087
88 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
89 protected OpenstackSecurityGroupService securityGroupService;
90
91 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
92 protected FlowObjectiveService flowObjectiveService;
93
94 private final InstancePortListener instancePortListener = new InternalInstancePortListener();
95 private final OpenstackNetworkListener portListener = new InternalOpenstackPortListener();
96 private final OpenstackSecurityGroupListener securityGroupListener = new InternalSecurityGroupListener();
97 private ApplicationId appId;
98
99 private final ExecutorService eventExecutor = newSingleThreadExecutor(
100 groupedThreads(this.getClass().getSimpleName(), "event-handler"));
101
102 private static final String PROTO_ICMP = "ICMP";
103 private static final String PROTO_TCP = "TCP";
104 private static final String PROTO_UDP = "UDP";
105 private static final String ETHTYPE_IPV4 = "IPV4";
106 private static final String EGRESS = "EGRESS";
107 private static final String INGRESS = "INGRESS";
108 private static final IpPrefix IP_PREFIX_ANY = Ip4Prefix.valueOf("0.0.0.0/0");
109
110 @Activate
111 protected void activate() {
112 appId = coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
113 instancePortService.addListener(instancePortListener);
114 securityGroupService.addListener(securityGroupListener);
Hyunsun Moonae51e732017-04-25 17:46:21 +0900115 osNetService.addListener(portListener);
sangho6a9ff0d2017-03-27 11:23:37 +0900116
117 log.info("Started");
118 }
119
120 @Deactivate
121 protected void deactivate() {
122 instancePortService.removeListener(instancePortListener);
123 securityGroupService.removeListener(securityGroupListener);
Hyunsun Moonae51e732017-04-25 17:46:21 +0900124 osNetService.removeListener(portListener);
sangho6a9ff0d2017-03-27 11:23:37 +0900125 eventExecutor.shutdown();
126
127 log.info("Stopped");
128 }
129
130 private void setSecurityGroupRules(InstancePort instPort, Port port, boolean install) {
131 port.getSecurityGroups().forEach(sgId -> {
sangho6a9ff0d2017-03-27 11:23:37 +0900132 SecurityGroup sg = securityGroupService.securityGroup(sgId);
133 if (sg == null) {
134 log.error("Security Group Not Found : {}", sgId);
135 return;
136 }
137 sg.getRules().forEach(sgRule -> updateSecurityGroupRule(instPort, port, sgRule, install));
Hyunsun Moonae51e732017-04-25 17:46:21 +0900138 final String action = install ? "Installed " : "Removed ";
139 log.debug(action + "security group rule ID : " + sgId);
sangho6a9ff0d2017-03-27 11:23:37 +0900140 });
141 }
142
143 private void updateSecurityGroupRule(InstancePort instPort, Port port, SecurityGroupRule sgRule, boolean install) {
144 if (sgRule.getRemoteGroupId() != null && !sgRule.getRemoteGroupId().isEmpty()) {
145 getRemoteInstPorts(port.getTenantId(), sgRule.getRemoteGroupId())
146 .forEach(rInstPort -> {
147 populateSecurityGroupRule(sgRule, instPort, rInstPort.ipAddress().toIpPrefix(), install);
148 populateSecurityGroupRule(sgRule, rInstPort, instPort.ipAddress().toIpPrefix(), install);
149
150 SecurityGroupRule rSgRule = new NeutronSecurityGroupRule.SecurityGroupRuleConcreteBuilder()
151 .from(sgRule)
152 .direction(sgRule.getDirection().toUpperCase().equals(EGRESS) ? INGRESS : EGRESS).build();
153 populateSecurityGroupRule(rSgRule, instPort, rInstPort.ipAddress().toIpPrefix(), install);
154 populateSecurityGroupRule(rSgRule, rInstPort, instPort.ipAddress().toIpPrefix(), install);
155 });
156 } else {
157 populateSecurityGroupRule(sgRule, instPort, sgRule.getRemoteIpPrefix() == null ? IP_PREFIX_ANY :
158 IpPrefix.valueOf(sgRule.getRemoteIpPrefix()), install);
159 }
160 }
161
162 private void populateSecurityGroupRule(SecurityGroupRule sgRule, InstancePort instPort,
163 IpPrefix remoteIp, boolean install) {
164 ForwardingObjective.Builder foBuilder = buildFlowObjective(sgRule,
165 Ip4Address.valueOf(instPort.ipAddress().toInetAddress()), remoteIp);
166 if (foBuilder == null) {
167 return;
168 }
169
170 if (install) {
171 flowObjectiveService.forward(instPort.deviceId(), foBuilder.add());
172 } else {
173 flowObjectiveService.forward(instPort.deviceId(), foBuilder.remove());
174 }
175 }
176
177 /**
178 * Returns a set of host IP addresses engaged with supplied security group ID.
179 * It only searches a VM in the same tenant boundary.
180 *
181 * @param tenantId tenant id
182 * @param sgId security group id
183 * @return set of ip addresses
184 */
185 private Set<InstancePort> getRemoteInstPorts(String tenantId, String sgId) {
186 Set<InstancePort> remoteInstPorts;
187
Hyunsun Moonae51e732017-04-25 17:46:21 +0900188 remoteInstPorts = osNetService.ports().stream()
sangho6a9ff0d2017-03-27 11:23:37 +0900189 .filter(port -> port.getTenantId().equals(tenantId))
190 .filter(port -> port.getSecurityGroups().contains(sgId))
191 .map(port -> instancePortService.instancePort(port.getId()))
192 .filter(instPort -> instPort != null && instPort.ipAddress() != null)
193 .collect(Collectors.toSet());
194
195 return Collections.unmodifiableSet(remoteInstPorts);
196 }
197
198 private ForwardingObjective.Builder buildFlowObjective(SecurityGroupRule sgRule,
199 Ip4Address vmIp,
200 IpPrefix remoteIp) {
201 if (remoteIp != null && remoteIp.equals(IpPrefix.valueOf(vmIp, 32))) {
202 // do nothing if the remote IP is my IP
203 return null;
204 }
205
206 TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
207 buildMatchs(sBuilder, sgRule, vmIp, remoteIp);
208
209 return DefaultForwardingObjective.builder()
210 .withSelector(sBuilder.build())
211 .withTreatment(DefaultTrafficTreatment.builder().build())
212 .withPriority(PRIORITY_ACL_RULE)
213 .withFlag(ForwardingObjective.Flag.SPECIFIC)
214 .fromApp(appId);
215 }
216
217 private void buildMatchs(TrafficSelector.Builder sBuilder, SecurityGroupRule sgRule,
218 Ip4Address vmIp, IpPrefix remoteIp) {
219 buildMatchEthType(sBuilder, sgRule.getEtherType());
220 buildMatchDirection(sBuilder, sgRule.getDirection(), vmIp);
221 buildMatchProto(sBuilder, sgRule.getProtocol());
222 buildMatchPort(sBuilder, sgRule.getProtocol(), sgRule.getDirection(),
223 sgRule.getPortRangeMax() == null ? 0 : sgRule.getPortRangeMax(),
224 sgRule.getPortRangeMin() == null ? 0 : sgRule.getPortRangeMin());
225 buildMatchRemoteIp(sBuilder, remoteIp, sgRule.getDirection());
226 if (sgRule.getRemoteGroupId() != null && sgRule.getRemoteGroupId().isEmpty()) {
227 buildMatchRemoteIp(sBuilder, remoteIp, sgRule.getDirection());
228 }
229 }
230
231 private void buildMatchDirection(TrafficSelector.Builder sBuilder,
232 String direction,
233 Ip4Address vmIp) {
234 if (direction.toUpperCase().equals(EGRESS)) {
235 sBuilder.matchIPSrc(IpPrefix.valueOf(vmIp, 32));
236 } else {
237 sBuilder.matchIPDst(IpPrefix.valueOf(vmIp, 32));
238 }
239 }
240
241 private void buildMatchEthType(TrafficSelector.Builder sBuilder, String etherType) {
242 // Either IpSrc or IpDst (or both) is set by default, and we need to set EthType as IPv4.
243 sBuilder.matchEthType(Ethernet.TYPE_IPV4);
244 if (etherType != null && !Objects.equals(etherType, "null") &&
245 !etherType.toUpperCase().equals(ETHTYPE_IPV4)) {
246 log.debug("EthType {} is not supported yet in Security Group", etherType);
247 }
248 }
249
250 private void buildMatchRemoteIp(TrafficSelector.Builder sBuilder, IpPrefix remoteIpPrefix, String direction) {
251 if (remoteIpPrefix != null && !remoteIpPrefix.getIp4Prefix().equals(IP_PREFIX_ANY)) {
252 if (direction.toUpperCase().equals(EGRESS)) {
253 sBuilder.matchIPDst(remoteIpPrefix);
254 } else {
255 sBuilder.matchIPSrc(remoteIpPrefix);
256 }
257 }
258 }
259
260 private void buildMatchProto(TrafficSelector.Builder sBuilder, String protocol) {
261 if (protocol != null) {
262 switch (protocol.toUpperCase()) {
263 case PROTO_ICMP:
264 sBuilder.matchIPProtocol(IPv4.PROTOCOL_ICMP);
265 break;
266 case PROTO_TCP:
267 sBuilder.matchIPProtocol(IPv4.PROTOCOL_TCP);
268 break;
269 case PROTO_UDP:
270 sBuilder.matchIPProtocol(IPv4.PROTOCOL_UDP);
271 break;
272 default:
273 }
274 }
275 }
276
277 private void buildMatchPort(TrafficSelector.Builder sBuilder, String protocol, String direction,
278 int portMin, int portMax) {
279 if (portMin > 0 && portMax > 0 && portMin == portMax) {
280 if (protocol.toUpperCase().equals(PROTO_TCP)) {
281 if (direction.toUpperCase().equals(EGRESS)) {
282 sBuilder.matchTcpSrc(TpPort.tpPort(portMax));
283 } else {
284 sBuilder.matchTcpDst(TpPort.tpPort(portMax));
285 }
286 } else if (protocol.toUpperCase().equals(PROTO_UDP)) {
287 if (direction.toUpperCase().equals(EGRESS)) {
288 sBuilder.matchUdpSrc(TpPort.tpPort(portMax));
289 } else {
290 sBuilder.matchUdpDst(TpPort.tpPort(portMax));
291 }
292 }
293 }
294 }
295
296 private class InternalInstancePortListener implements InstancePortListener {
297
298 @Override
299 public boolean isRelevant(InstancePortEvent event) {
300 InstancePort instPort = event.subject();
301 return mastershipService.isLocalMaster(instPort.deviceId());
302 }
303
304 @Override
305 public void event(InstancePortEvent event) {
306 InstancePort instPort = event.subject();
307 switch (event.type()) {
308 case OPENSTACK_INSTANCE_PORT_UPDATED:
309 case OPENSTACK_INSTANCE_PORT_DETECTED:
Hyunsun Moonae51e732017-04-25 17:46:21 +0900310 log.debug("Instance port detected MAC:{} IP:{}",
311 instPort.macAddress(),
312 instPort.ipAddress());
sangho6a9ff0d2017-03-27 11:23:37 +0900313 eventExecutor.execute(() -> {
Hyunsun Moonae51e732017-04-25 17:46:21 +0900314 setSecurityGroupRules(instPort,
315 osNetService.port(event.subject().portId()),
316 true);
sangho6a9ff0d2017-03-27 11:23:37 +0900317 });
318 break;
319 case OPENSTACK_INSTANCE_PORT_VANISHED:
Hyunsun Moonae51e732017-04-25 17:46:21 +0900320 log.debug("Instance port vanished MAC:{} IP:{}",
321 instPort.macAddress(),
322 instPort.ipAddress());
sangho6a9ff0d2017-03-27 11:23:37 +0900323 eventExecutor.execute(() -> {
Hyunsun Moonae51e732017-04-25 17:46:21 +0900324 setSecurityGroupRules(instPort,
325 osNetService.port(event.subject().portId()),
326 false);
sangho6a9ff0d2017-03-27 11:23:37 +0900327 });
328 break;
329 default:
330 break;
331 }
332 }
sangho6a9ff0d2017-03-27 11:23:37 +0900333 }
334
335 private class InternalOpenstackPortListener implements OpenstackNetworkListener {
336
337 @Override
338 public boolean isRelevant(OpenstackNetworkEvent event) {
Hyunsun Moonae51e732017-04-25 17:46:21 +0900339 if (event.port() == null || !Strings.isNullOrEmpty(event.port().getId())) {
sangho6a9ff0d2017-03-27 11:23:37 +0900340 return false;
341 }
Hyunsun Moonae51e732017-04-25 17:46:21 +0900342 if (event.securityGroupId() == null ||
343 securityGroupService.securityGroup(event.securityGroupId()) == null) {
344 return false;
345 }
346 if (instancePortService.instancePort(event.port().getId()) == null) {
347 return false;
348 }
349 return true;
sangho6a9ff0d2017-03-27 11:23:37 +0900350 }
351
352 @Override
353 public void event(OpenstackNetworkEvent event) {
Hyunsun Moonae51e732017-04-25 17:46:21 +0900354 Port osPort = event.port();
355 InstancePort instPort = instancePortService.instancePort(osPort.getId());
356 SecurityGroup osSg = securityGroupService.securityGroup(event.securityGroupId());
357
sangho6a9ff0d2017-03-27 11:23:37 +0900358 switch (event.type()) {
Hyunsun Moonae51e732017-04-25 17:46:21 +0900359 case OPENSTACK_PORT_SECURITY_GROUP_ADDED:
360 eventExecutor.execute(() -> {
361 osSg.getRules().forEach(sgRule -> {
362 updateSecurityGroupRule(instPort, osPort, sgRule, true);
363 });
364 log.info("Added security group {} to port {}",
365 event.securityGroupId(), event.port().getId());
366 });
sangho6a9ff0d2017-03-27 11:23:37 +0900367 break;
Hyunsun Moonae51e732017-04-25 17:46:21 +0900368 case OPENSTACK_PORT_SECURITY_GROUP_REMOVED:
369 eventExecutor.execute(() -> {
370 osSg.getRules().forEach(sgRule -> {
371 updateSecurityGroupRule(instPort, osPort, sgRule, false);
372 });
373 log.info("Removed security group {} from port {}",
374 event.securityGroupId(), event.port().getId());
375 });
sangho6a9ff0d2017-03-27 11:23:37 +0900376 break;
377 default:
Hyunsun Moonae51e732017-04-25 17:46:21 +0900378 // do nothing for the other events
sangho6a9ff0d2017-03-27 11:23:37 +0900379 break;
380 }
381 }
sangho6a9ff0d2017-03-27 11:23:37 +0900382 }
383
384 private class InternalSecurityGroupListener implements OpenstackSecurityGroupListener {
385
386 @Override
387 public void event(OpenstackSecurityGroupEvent event) {
388 switch (event.type()) {
sangho6a9ff0d2017-03-27 11:23:37 +0900389 case OPENSTACK_SECURITY_GROUP_RULE_CREATED:
390 SecurityGroupRule securityGroupRuleToAdd = event.securityGroupRule();
391 eventExecutor.execute(() -> {
sangho6a9ff0d2017-03-27 11:23:37 +0900392 securityGroupRuleAdded(securityGroupRuleToAdd);
Hyunsun Moonae51e732017-04-25 17:46:21 +0900393 log.info("Applied new security group rule {} to ports",
394 securityGroupRuleToAdd.getId());
sangho6a9ff0d2017-03-27 11:23:37 +0900395 });
396 break;
397
398 case OPENSTACK_SECURITY_GROUP_RULE_REMOVED:
399 SecurityGroupRule securityGroupRuleToRemove = event.securityGroupRule();
400 eventExecutor.execute(() -> {
sangho6a9ff0d2017-03-27 11:23:37 +0900401 securityGroupRuleRemoved(securityGroupRuleToRemove);
Hyunsun Moonae51e732017-04-25 17:46:21 +0900402 log.info("Removed security group rule {} from ports",
403 securityGroupRuleToRemove.getId());
sangho6a9ff0d2017-03-27 11:23:37 +0900404 });
405 break;
Hyunsun Moonae51e732017-04-25 17:46:21 +0900406 case OPENSTACK_SECURITY_GROUP_CREATED:
407 case OPENSTACK_SECURITY_GROUP_REMOVED:
sangho6a9ff0d2017-03-27 11:23:37 +0900408 default:
Hyunsun Moonae51e732017-04-25 17:46:21 +0900409 // do nothing
410 break;
sangho6a9ff0d2017-03-27 11:23:37 +0900411 }
412 }
413
414 private void securityGroupRuleAdded(SecurityGroupRule sgRule) {
Hyunsun Moonae51e732017-04-25 17:46:21 +0900415 osNetService.ports().stream()
sangho6a9ff0d2017-03-27 11:23:37 +0900416 .filter(port -> port.getSecurityGroups().contains(sgRule.getSecurityGroupId()))
Hyunsun Moonae51e732017-04-25 17:46:21 +0900417 .forEach(port -> {
418 updateSecurityGroupRule(
419 instancePortService.instancePort(port.getId()),
420 port, sgRule, true);
421 log.debug("Applied security group rule {} to port {}",
422 sgRule.getId(), port.getId());
423 });
sangho6a9ff0d2017-03-27 11:23:37 +0900424 }
425
426 private void securityGroupRuleRemoved(SecurityGroupRule sgRule) {
Hyunsun Moonae51e732017-04-25 17:46:21 +0900427 osNetService.ports().stream()
sangho6a9ff0d2017-03-27 11:23:37 +0900428 .filter(port -> port.getSecurityGroups().contains(sgRule.getSecurityGroupId()))
Hyunsun Moonae51e732017-04-25 17:46:21 +0900429 .forEach(port -> {
430 updateSecurityGroupRule(
431 instancePortService.instancePort(port.getId()),
432 port, sgRule, false);
433 log.debug("Removed security group rule {} from port {}",
434 sgRule.getId(), port.getId());
435 });
sangho6a9ff0d2017-03-27 11:23:37 +0900436 }
437 }
438}