blob: a3f135b0cf385c56bb7a54a4236d720333d36c9d [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
56import java.util.Collection;
57import java.util.Collections;
58import java.util.Objects;
59import java.util.Set;
60import java.util.concurrent.ExecutorService;
61import java.util.stream.Collectors;
62
63import static java.util.concurrent.Executors.newSingleThreadExecutor;
64import static org.onlab.util.Tools.groupedThreads;
65import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
66import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_ACL_RULE;
67import static org.slf4j.LoggerFactory.getLogger;
68
69/**
70 * Populates flow rules to handle OpenStack SecurityGroups.
71 */
72@Component(immediate = true)
73public class OpenstackSecurityGroupHandler {
74
75 private final Logger log = getLogger(getClass());
76
77 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
78 protected CoreService coreService;
79
80 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
81 protected InstancePortService instancePortService;
82
83 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
84 protected MastershipService mastershipService;
85
86 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
87 protected OpenstackNetworkService openstackService;
88
89 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
90 protected OpenstackSecurityGroupService securityGroupService;
91
92 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
93 protected FlowObjectiveService flowObjectiveService;
94
95 private final InstancePortListener instancePortListener = new InternalInstancePortListener();
96 private final OpenstackNetworkListener portListener = new InternalOpenstackPortListener();
97 private final OpenstackSecurityGroupListener securityGroupListener = new InternalSecurityGroupListener();
98 private ApplicationId appId;
99
100 private final ExecutorService eventExecutor = newSingleThreadExecutor(
101 groupedThreads(this.getClass().getSimpleName(), "event-handler"));
102
103 private static final String PROTO_ICMP = "ICMP";
104 private static final String PROTO_TCP = "TCP";
105 private static final String PROTO_UDP = "UDP";
106 private static final String ETHTYPE_IPV4 = "IPV4";
107 private static final String EGRESS = "EGRESS";
108 private static final String INGRESS = "INGRESS";
109 private static final IpPrefix IP_PREFIX_ANY = Ip4Prefix.valueOf("0.0.0.0/0");
110
111 @Activate
112 protected void activate() {
113 appId = coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
114 instancePortService.addListener(instancePortListener);
115 securityGroupService.addListener(securityGroupListener);
116 openstackService.addListener(portListener);
117
118 log.info("Started");
119 }
120
121 @Deactivate
122 protected void deactivate() {
123 instancePortService.removeListener(instancePortListener);
124 securityGroupService.removeListener(securityGroupListener);
125 openstackService.removeListener(portListener);
126 eventExecutor.shutdown();
127
128 log.info("Stopped");
129 }
130
131 private void setSecurityGroupRules(InstancePort instPort, Port port, boolean install) {
132 port.getSecurityGroups().forEach(sgId -> {
133 log.debug("security group rule ID : " + sgId.toString());
134 SecurityGroup sg = securityGroupService.securityGroup(sgId);
135 if (sg == null) {
136 log.error("Security Group Not Found : {}", sgId);
137 return;
138 }
139 sg.getRules().forEach(sgRule -> updateSecurityGroupRule(instPort, port, sgRule, install));
140 });
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
188 remoteInstPorts = openstackService.ports().stream()
189 .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:
310 eventExecutor.execute(() -> {
311 log.info("Instance port detected MAC:{} IP:{}",
312 instPort.macAddress(),
313 instPort.ipAddress());
314 instPortDetected(event.subject(), openstackService.port(event.subject().portId()));
315 });
316 break;
317 case OPENSTACK_INSTANCE_PORT_VANISHED:
318 eventExecutor.execute(() -> {
319 log.info("Instance port vanished MAC:{} IP:{}",
320 instPort.macAddress(),
321 instPort.ipAddress());
322 instPortRemoved(event.subject(), openstackService.port(event.subject().portId()));
323 });
324 break;
325 default:
326 break;
327 }
328 }
329
330 private void instPortDetected(InstancePort instPort, Port port) {
331 setSecurityGroupRules(instPort, port, true);
332 }
333
334 private void instPortRemoved(InstancePort instPort, Port port) {
335 setSecurityGroupRules(instPort, port, false);
336 }
337 }
338
339 private class InternalOpenstackPortListener implements OpenstackNetworkListener {
340
341 @Override
342 public boolean isRelevant(OpenstackNetworkEvent event) {
343 Port osPort = event.port();
344 if (osPort == null) {
345 return false;
346 }
347 return !Strings.isNullOrEmpty(osPort.getId());
348 }
349
350 @Override
351 public void event(OpenstackNetworkEvent event) {
352 switch (event.type()) {
353 case OPENSTACK_SECURITY_GROUP_ADDED_TO_PORT:
354 securityGroupAddedToPort(event.securityGroupRuleIds(), event.port());
355 break;
356 case OPENSTACK_SECURITY_GROUP_REMOVED_FROM_PORT:
357 securityGroupRemovedFromPort(event.securityGroupRuleIds(), event.port());
358 break;
359 default:
360 break;
361 }
362 }
363
364 private void securityGroupAddedToPort(Collection<String> sgToAdd, Port osPort) {
365 sgToAdd.forEach(sg -> {
366 InstancePort instPort = instancePortService.instancePort(osPort.getId());
367 if (instPort != null) {
368 securityGroupService.securityGroup(sg).getRules().stream()
369 .forEach(sgRule -> updateSecurityGroupRule(instancePortService.instancePort(
370 osPort.getId()), osPort, sgRule, true));
371 }
372 });
373 }
374
375 private void securityGroupRemovedFromPort(Collection<String> sgToRemove, Port osPort) {
376 sgToRemove.forEach(sg -> {
377 InstancePort instPort = instancePortService.instancePort(osPort.getId());
378 if (instPort != null) {
379 securityGroupService.securityGroup(sg).getRules().stream()
380 .forEach(sgRule -> updateSecurityGroupRule(instancePortService.instancePort(
381 osPort.getId()), osPort, sgRule, false));
382 }
383 });
384 }
385 }
386
387 private class InternalSecurityGroupListener implements OpenstackSecurityGroupListener {
388
389 @Override
390 public void event(OpenstackSecurityGroupEvent event) {
391 switch (event.type()) {
392 case OPENSTACK_SECURITY_GROUP_CREATED:
393 case OPENSTACK_SECURITY_GROUP_REMOVED:
394 break;
395 case OPENSTACK_SECURITY_GROUP_RULE_CREATED:
396 SecurityGroupRule securityGroupRuleToAdd = event.securityGroupRule();
397 eventExecutor.execute(() -> {
398 log.info("Security group rule detected: ID {}",
399 securityGroupRuleToAdd.getId());
400 securityGroupRuleAdded(securityGroupRuleToAdd);
401 });
402 break;
403
404 case OPENSTACK_SECURITY_GROUP_RULE_REMOVED:
405 SecurityGroupRule securityGroupRuleToRemove = event.securityGroupRule();
406 eventExecutor.execute(() -> {
407 log.info("security gorup rule removed: ID {}",
408 securityGroupRuleToRemove.getId());
409 securityGroupRuleRemoved(securityGroupRuleToRemove);
410 });
411 break;
412 default:
413 }
414 }
415
416 private void securityGroupRuleAdded(SecurityGroupRule sgRule) {
417 log.debug("securityGroupRuleAdded : {}" + sgRule);
418
419 openstackService.ports().stream()
420 .filter(port -> port.getSecurityGroups().contains(sgRule.getSecurityGroupId()))
421 .forEach(port -> updateSecurityGroupRule(instancePortService.instancePort(port.getId()),
422 port, sgRule, true));
423 }
424
425 private void securityGroupRuleRemoved(SecurityGroupRule sgRule) {
426 log.debug("securityGroupRuleRemoved : {}" + sgRule);
427
428 openstackService.ports().stream()
429 .filter(port -> port.getSecurityGroups().contains(sgRule.getSecurityGroupId()))
430 .forEach(port -> updateSecurityGroupRule(instancePortService.instancePort(port.getId()),
431 port, sgRule, false));
432 }
433 }
434}