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