blob: 9b15f00f84818d72dac71af05ea3f92db5a169b8 [file] [log] [blame]
Hyunsun Moonb974fca2016-06-30 21:20:39 -07001/*
2* Copyright 2016-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.switching;
18
19import com.google.common.collect.Maps;
20import com.google.common.collect.Sets;
21import org.apache.felix.scr.annotations.Activate;
22import org.apache.felix.scr.annotations.Component;
23import org.apache.felix.scr.annotations.Deactivate;
24import org.apache.felix.scr.annotations.Reference;
25import org.apache.felix.scr.annotations.ReferenceCardinality;
sanghoc853a722016-07-04 21:10:42 +090026import org.apache.felix.scr.annotations.Service;
Hyunsun Moonb974fca2016-06-30 21:20:39 -070027import org.onlab.packet.Ethernet;
28import org.onlab.packet.IPv4;
29import org.onlab.packet.Ip4Address;
30import org.onlab.packet.IpPrefix;
31import org.onlab.packet.TpPort;
32import org.onlab.util.Tools;
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070033import org.onosproject.core.ApplicationId;
Hyunsun Moonb974fca2016-06-30 21:20:39 -070034import org.onosproject.net.DeviceId;
35import org.onosproject.net.Host;
36import org.onosproject.net.flow.DefaultTrafficSelector;
37import org.onosproject.net.flow.DefaultTrafficTreatment;
38import org.onosproject.net.flow.TrafficSelector;
39import org.onosproject.net.flowobjective.DefaultForwardingObjective;
40import org.onosproject.net.flowobjective.FlowObjectiveService;
41import org.onosproject.net.flowobjective.ForwardingObjective;
42import org.onosproject.openstackinterface.OpenstackInterfaceService;
43import org.onosproject.openstackinterface.OpenstackPort;
44import org.onosproject.openstackinterface.OpenstackSecurityGroup;
45import org.onosproject.openstackinterface.OpenstackSecurityGroupRule;
sanghoc853a722016-07-04 21:10:42 +090046import org.onosproject.openstacknetworking.OpenstackSecurityGroupService;
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070047import org.onosproject.openstacknetworking.AbstractVmHandler;
Hyunsun Moonb974fca2016-06-30 21:20:39 -070048import org.slf4j.Logger;
49import org.slf4j.LoggerFactory;
50
51import java.util.Map;
52import java.util.Objects;
53import java.util.Optional;
54import java.util.Set;
55import java.util.stream.Collectors;
56
sangho6032f342016-07-07 14:32:03 +090057import static org.onosproject.openstacknetworking.Constants.*;
Hyunsun Moonb974fca2016-06-30 21:20:39 -070058
59/**
60 * Populates flows rules for Security Groups of VMs.
61 *
62 */
63@Component(immediate = true)
sanghoc853a722016-07-04 21:10:42 +090064@Service
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070065public class OpenstackSecurityGroupManager extends AbstractVmHandler
sanghoc853a722016-07-04 21:10:42 +090066 implements OpenstackSecurityGroupService {
Hyunsun Moonb974fca2016-06-30 21:20:39 -070067
68 private final Logger log = LoggerFactory.getLogger(getClass());
69
70 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
71 protected OpenstackInterfaceService openstackService;
72
73 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
74 protected FlowObjectiveService flowObjectiveService;
75
76 private static final String PROTO_ICMP = "ICMP";
77 private static final String PROTO_TCP = "TCP";
78 private static final String PROTO_UDP = "UDP";
79 private static final String ETHTYPE_IPV4 = "IPV4";
80
81 private final Map<Host, Set<SecurityGroupRule>> securityGroupRuleMap = Maps.newConcurrentMap();
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070082 private ApplicationId appId;
Hyunsun Moonb974fca2016-06-30 21:20:39 -070083
84 @Activate
85 protected void activate() {
86 super.activate();
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070087 appId = coreService.registerApplication(SWITCHING_APP_ID);
Hyunsun Moonb974fca2016-06-30 21:20:39 -070088 }
89
90 @Deactivate
91 protected void deactivate() {
92 super.deactivate();
93 }
94
sanghoc853a722016-07-04 21:10:42 +090095 @Override
Hyunsun Moonb974fca2016-06-30 21:20:39 -070096 public void updateSecurityGroup(OpenstackPort osPort) {
97 if (!osPort.status().equals(OpenstackPort.PortStatus.ACTIVE)) {
98 return;
99 }
100
101 Optional<Host> host = getVmByPortId(osPort.id());
102 if (!host.isPresent()) {
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700103 log.debug("No host found with {}", osPort.id());
Hyunsun Moonb974fca2016-06-30 21:20:39 -0700104 return;
105 }
106 eventExecutor.execute(() -> updateSecurityGroupRules(host.get(), true));
107 }
108
109 /**
110 * Populates security group rules for all VMs in the supplied tenant ID.
111 * VMs in the same tenant tend to be engaged to each other by sharing the
112 * same security groups or setting the remote to another security group.
113 * To make the implementation simpler and robust, it tries to reinstall
114 * security group rules for all the VMs in the same tenant whenever a new
115 * VM is detected or port is updated.
116 *
117 * @param tenantId tenant id to update security group rules
118 */
119 private void populateSecurityGroupRules(String tenantId, boolean install) {
120 securityGroupRuleMap.entrySet().stream()
121 .filter(entry -> getTenantId(entry.getKey()).equals(tenantId))
122 .forEach(entry -> {
123 Host local = entry.getKey();
Sho SHIMIZUa09e1bb2016-08-01 14:25:25 -0700124 entry.getValue().forEach(sgRule -> {
Hyunsun Moonb974fca2016-06-30 21:20:39 -0700125 setSecurityGroupRule(local.location().deviceId(),
126 sgRule.rule(),
127 getIp(local),
128 sgRule.remoteIp(), install);
129 });
130 });
131 log.debug("Updated security group rules for {}", tenantId);
132 }
133
134 private void setSecurityGroupRule(DeviceId deviceId, OpenstackSecurityGroupRule sgRule,
135 Ip4Address vmIp, IpPrefix remoteIp,
136 boolean install) {
137 ForwardingObjective.Builder foBuilder = buildFlowObjective(sgRule, vmIp, remoteIp);
138 if (foBuilder == null) {
139 return;
140 }
141
142 if (install) {
143 flowObjectiveService.forward(deviceId, foBuilder.add());
144 } else {
145 flowObjectiveService.forward(deviceId, foBuilder.remove());
146 }
147 }
148
149 private ForwardingObjective.Builder buildFlowObjective(OpenstackSecurityGroupRule sgRule,
150 Ip4Address vmIp,
151 IpPrefix remoteIp) {
152 if (remoteIp != null && remoteIp.equals(IpPrefix.valueOf(vmIp, 32))) {
153 // do nothing if the remote IP is my IP
154 return null;
155 }
156
157 TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
158 buildMatchs(sBuilder, sgRule, vmIp, remoteIp);
159
160 return DefaultForwardingObjective.builder()
161 .withSelector(sBuilder.build())
162 .withTreatment(DefaultTrafficTreatment.builder().build())
163 .withPriority(ACL_RULE_PRIORITY)
164 .withFlag(ForwardingObjective.Flag.SPECIFIC)
165 .fromApp(appId);
166 }
167
168 private void buildMatchs(TrafficSelector.Builder sBuilder, OpenstackSecurityGroupRule sgRule,
169 Ip4Address vmIp, IpPrefix remoteIp) {
170 buildMatchEthType(sBuilder, sgRule.ethertype());
171 buildMatchDirection(sBuilder, sgRule.direction(), vmIp);
172 buildMatchProto(sBuilder, sgRule.protocol());
173 buildMatchPort(sBuilder, sgRule.protocol(), sgRule.direction(),
174 sgRule.portRangeMax(), sgRule.portRangeMin());
175 buildMatchRemoteIp(sBuilder, remoteIp, sgRule.direction());
176 }
177
178 private void buildMatchDirection(TrafficSelector.Builder sBuilder,
179 OpenstackSecurityGroupRule.Direction direction,
180 Ip4Address vmIp) {
181 if (direction.equals(OpenstackSecurityGroupRule.Direction.EGRESS)) {
182 sBuilder.matchIPSrc(IpPrefix.valueOf(vmIp, 32));
183 } else {
184 sBuilder.matchIPDst(IpPrefix.valueOf(vmIp, 32));
185 }
186 }
187
188 private void buildMatchEthType(TrafficSelector.Builder sBuilder, String etherType) {
189 // Either IpSrc or IpDst (or both) is set by default, and we need to set EthType as IPv4.
190 sBuilder.matchEthType(Ethernet.TYPE_IPV4);
191 if (etherType != null && !Objects.equals(etherType, "null") &&
192 !etherType.toUpperCase().equals(ETHTYPE_IPV4)) {
193 log.debug("EthType {} is not supported yet in Security Group", etherType);
194 }
195 }
196
197 private void buildMatchRemoteIp(TrafficSelector.Builder sBuilder, IpPrefix remoteIpPrefix,
198 OpenstackSecurityGroupRule.Direction direction) {
199 if (remoteIpPrefix != null && !remoteIpPrefix.getIp4Prefix().equals(IP_PREFIX_ANY)) {
200 if (direction.equals(OpenstackSecurityGroupRule.Direction.EGRESS)) {
201 sBuilder.matchIPDst(remoteIpPrefix);
202 } else {
203 sBuilder.matchIPSrc(remoteIpPrefix);
204 }
205 }
206 }
207
208 private void buildMatchProto(TrafficSelector.Builder sBuilder, String protocol) {
209 if (protocol != null) {
210 switch (protocol.toUpperCase()) {
211 case PROTO_ICMP:
212 sBuilder.matchIPProtocol(IPv4.PROTOCOL_ICMP);
213 break;
214 case PROTO_TCP:
215 sBuilder.matchIPProtocol(IPv4.PROTOCOL_TCP);
216 break;
217 case PROTO_UDP:
218 sBuilder.matchIPProtocol(IPv4.PROTOCOL_UDP);
219 break;
220 default:
221 }
222 }
223 }
224
225 private void buildMatchPort(TrafficSelector.Builder sBuilder, String protocol,
226 OpenstackSecurityGroupRule.Direction direction,
227 int portMin, int portMax) {
228 if (portMin > 0 && portMax > 0 && portMin == portMax) {
229 if (protocol.toUpperCase().equals(PROTO_TCP)) {
230 if (direction.equals(OpenstackSecurityGroupRule.Direction.EGRESS)) {
231 sBuilder.matchTcpDst(TpPort.tpPort(portMax));
232 } else {
233 sBuilder.matchTcpSrc(TpPort.tpPort(portMax));
234 }
235 } else if (protocol.toUpperCase().equals(PROTO_UDP)) {
236 if (direction.equals(OpenstackSecurityGroupRule.Direction.EGRESS)) {
237 sBuilder.matchUdpDst(TpPort.tpPort(portMax));
238 } else {
239 sBuilder.matchUdpSrc(TpPort.tpPort(portMax));
240 }
241 }
242 }
243 }
244
245 private void updateSecurityGroupRulesMap(Host host) {
246 OpenstackPort osPort = openstackService.port(host.annotations().value(PORT_ID));
247 if (osPort == null) {
248 log.debug("Failed to get OpenStack port information for {}", host);
249 return;
250 }
251
252 Set<SecurityGroupRule> rules = Sets.newHashSet();
Sho SHIMIZUa09e1bb2016-08-01 14:25:25 -0700253 osPort.securityGroups().forEach(sgId -> {
Hyunsun Moonb974fca2016-06-30 21:20:39 -0700254 OpenstackSecurityGroup osSecGroup = openstackService.securityGroup(sgId);
255 if (osSecGroup != null) {
Sho SHIMIZUa09e1bb2016-08-01 14:25:25 -0700256 osSecGroup.rules().forEach(rule -> rules.addAll(getSgRules(rule)));
Hyunsun Moonb974fca2016-06-30 21:20:39 -0700257 } else {
258 // TODO handle the case that the security group removed
259 log.warn("Failed to get security group {}", sgId);
260 }
261 });
262 securityGroupRuleMap.put(host, rules);
263 }
264
265 /**
266 * Returns set of security group rules with individual remote IP by
267 * converting remote group to actual IP address.
268 *
269 * @param sgRule security group rule
270 * @return set of security group rules
271 */
272 private Set<SecurityGroupRule> getSgRules(OpenstackSecurityGroupRule sgRule) {
273 Set<SecurityGroupRule> sgRules = Sets.newHashSet();
274 if (sgRule.remoteGroupId() != null && !sgRule.remoteGroupId().equals("null")) {
275 sgRules = getRemoteIps(sgRule.tenantId(), sgRule.remoteGroupId())
276 .stream()
277 .map(remoteIp -> new SecurityGroupRule(sgRule, remoteIp))
278 .collect(Collectors.toSet());
279 } else {
280 sgRules.add(new SecurityGroupRule(sgRule, sgRule.remoteIpPrefix()));
281 }
282 return sgRules;
283 }
284
285 /**
286 * Returns a set of host IP addresses engaged with supplied security group ID.
287 * It only searches a VM in the same tenant boundary.
288 *
289 * @param tenantId tenant id
290 * @param sgId security group id
291 * @return set of ip addresses in ip prefix format
292 */
293 private Set<IpPrefix> getRemoteIps(String tenantId, String sgId) {
294 Set<IpPrefix> remoteIps = Sets.newHashSet();
295 securityGroupRuleMap.entrySet().stream()
296 .filter(entry -> Objects.equals(getTenantId(entry.getKey()), tenantId))
297 .forEach(entry -> {
298 if (entry.getValue().stream()
299 .anyMatch(rule -> rule.rule().secuityGroupId().equals(sgId))) {
300 remoteIps.add(IpPrefix.valueOf(getIp(entry.getKey()), 32));
301 }
302 });
303 return remoteIps;
304 }
305
306 private void updateSecurityGroupRules(Host host, boolean isHostAdded) {
307 String tenantId = getTenantId(host);
308 populateSecurityGroupRules(tenantId, false);
309
310 if (isHostAdded) {
311 updateSecurityGroupRulesMap(host);
312 } else {
313 securityGroupRuleMap.remove(host);
314 }
315
316 Tools.stream(hostService.getHosts())
317 .filter(h -> Objects.equals(getTenantId(h), getTenantId(host)))
318 .forEach(this::updateSecurityGroupRulesMap);
319
320 populateSecurityGroupRules(tenantId, true);
321 }
322
323 @Override
324 protected void hostDetected(Host host) {
325 updateSecurityGroupRules(host, true);
326 log.info("Applied security group rules for {}", host);
327 }
328
329 @Override
330 protected void hostRemoved(Host host) {
331 updateSecurityGroupRules(host, false);
332 log.info("Applied security group rules for {}", host);
333 }
334
335 private final class SecurityGroupRule {
336 private final OpenstackSecurityGroupRule rule;
337 private final IpPrefix remoteIp;
338
339 private SecurityGroupRule(OpenstackSecurityGroupRule rule, IpPrefix remoteIp) {
340 this.rule = rule;
341 this.remoteIp = remoteIp;
342 }
343
344 private OpenstackSecurityGroupRule rule() {
345 return rule;
346 }
347
348 private IpPrefix remoteIp() {
349 return remoteIp;
350 }
351
352 @Override
353 public boolean equals(Object obj) {
354 if (this == obj) {
355 return true;
356 }
357
358 if (obj instanceof SecurityGroupRule) {
359 SecurityGroupRule that = (SecurityGroupRule) obj;
360 if (Objects.equals(rule, that.rule) &&
361 Objects.equals(remoteIp, that.remoteIp)) {
362 return true;
363 }
364 }
365 return false;
366 }
367
368 @Override
369 public int hashCode() {
370 return Objects.hash(rule, remoteIp);
371 }
372 }
373}