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