blob: 21270190a509d456264e68f4fd0b26c4e62467bb [file] [log] [blame]
Simon Hunt5789e1c2017-11-08 15:34:07 -08001/*
2 * Copyright 2017-present Open Networking Foundation
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.segmentrouting;
18
19
Ray Milkey1567b532018-02-05 10:25:32 -080020import com.google.common.base.Objects;
Simon Hunt5789e1c2017-11-08 15:34:07 -080021import org.onosproject.net.ConnectPoint;
22import org.onosproject.net.DeviceId;
23import org.onosproject.net.PortNumber;
24import org.onosproject.segmentrouting.config.BlockedPortsConfig;
25import org.onosproject.utils.Comparators;
26import org.slf4j.Logger;
27
28import java.util.ArrayList;
29import java.util.Collections;
30import java.util.HashMap;
31import java.util.List;
32import java.util.Map;
33
34import static org.onosproject.net.DeviceId.deviceId;
35import static org.onosproject.net.PortNumber.portNumber;
36import static org.slf4j.LoggerFactory.getLogger;
37
38/**
39 * Keeps track of ports that have been configured for blocking,
40 * and their current authentication state.
41 */
42public class PortAuthTracker {
43
44 private static final Logger log = getLogger(PortAuthTracker.class);
45
46 private Map<DeviceId, Map<PortNumber, BlockState>> blockedPorts = new HashMap<>();
47 private Map<DeviceId, Map<PortNumber, BlockState>> oldMap;
48
49 @Override
50 public String toString() {
51 return "PortAuthTracker{entries = " + blockedPorts.size() + "}";
52 }
53
54 /**
55 * Changes the state of the given device id / port number pair to the
56 * specified state.
57 *
58 * @param d device identifier
59 * @param p port number
60 * @param newState the updated state
61 * @return true, if the state changed from what was previously mapped
62 */
63 private boolean changeStateTo(DeviceId d, PortNumber p, BlockState newState) {
64 Map<PortNumber, BlockState> portMap =
65 blockedPorts.computeIfAbsent(d, k -> new HashMap<>());
66 BlockState oldState =
67 portMap.computeIfAbsent(p, k -> BlockState.UNCHECKED);
68 portMap.put(p, newState);
69 return (oldState != newState);
70 }
71
72 /**
73 * Radius has authorized the supplicant at this connect point. If
74 * we are tracking this port, clear the blocking flow and mark the
75 * port as authorized.
76 *
77 * @param connectPoint supplicant connect point
78 */
79 void radiusAuthorize(ConnectPoint connectPoint) {
80 DeviceId d = connectPoint.deviceId();
81 PortNumber p = connectPoint.port();
82 if (configured(d, p)) {
83 clearBlockingFlow(d, p);
84 markAsAuthenticated(d, p);
85 }
86 }
87
88 /**
89 * Supplicant at specified connect point has logged off Radius. If
90 * we are tracking this port, install a blocking flow and mark the
91 * port as blocked.
92 *
93 * @param connectPoint supplicant connect point
94 */
95 void radiusLogoff(ConnectPoint connectPoint) {
96 DeviceId d = connectPoint.deviceId();
97 PortNumber p = connectPoint.port();
98 if (configured(d, p)) {
99 installBlockingFlow(d, p);
100 markAsBlocked(d, p);
101 }
102 }
103
104 /**
105 * Marks the specified device/port as blocked.
106 *
107 * @param d device id
108 * @param p port number
109 * @return true if the state changed (was not already blocked)
110 */
111 private boolean markAsBlocked(DeviceId d, PortNumber p) {
112 return changeStateTo(d, p, BlockState.BLOCKED);
113 }
114
115 /**
116 * Marks the specified device/port as authenticated.
117 *
118 * @param d device id
119 * @param p port number
120 * @return true if the state changed (was not already authenticated)
121 */
122 private boolean markAsAuthenticated(DeviceId d, PortNumber p) {
123 return changeStateTo(d, p, BlockState.AUTHENTICATED);
124 }
125
126 /**
127 * Returns true if the given device/port are configured for blocking.
128 *
129 * @param d device id
130 * @param p port number
131 * @return true if this device/port configured for blocking
132 */
133 private boolean configured(DeviceId d, PortNumber p) {
134 Map<PortNumber, BlockState> portMap = blockedPorts.get(d);
135 return portMap != null && portMap.get(p) != null;
136 }
137
138 private BlockState whatState(DeviceId d, PortNumber p,
139 Map<DeviceId, Map<PortNumber, BlockState>> m) {
140 Map<PortNumber, BlockState> portMap = m.get(d);
141 if (portMap == null) {
142 return BlockState.UNCHECKED;
143 }
144 BlockState state = portMap.get(p);
145 if (state == null) {
146 return BlockState.UNCHECKED;
147 }
148 return state;
149 }
150
151 /**
152 * Returns the current state of the given device/port.
153 *
154 * @param d device id
155 * @param p port number
156 * @return current block-state
157 */
158 BlockState currentState(DeviceId d, PortNumber p) {
159 return whatState(d, p, blockedPorts);
160 }
161
162 /**
163 * Returns the current state of the given connect point.
164 *
165 * @param cp connect point
166 * @return current block-state
167 */
168
169 BlockState currentState(ConnectPoint cp) {
170 return whatState(cp.deviceId(), cp.port(), blockedPorts);
171 }
172
173 /**
174 * Returns the number of entries being tracked.
175 *
176 * @return the number of tracked entries
177 */
178 int entryCount() {
179 int count = 0;
180 for (Map<PortNumber, BlockState> m : blockedPorts.values()) {
181 count += m.size();
182 }
183 return count;
184 }
185
186 /**
187 * Returns the previously recorded state of the given device/port.
188 *
189 * @param d device id
190 * @param p port number
191 * @return previous block-state
192 */
193 private BlockState oldState(DeviceId d, PortNumber p) {
194 return whatState(d, p, oldMap);
195 }
196
197 private void configurePort(DeviceId d, PortNumber p) {
198 boolean alreadyAuthenticated =
199 oldState(d, p) == BlockState.AUTHENTICATED;
200
201 if (alreadyAuthenticated) {
202 clearBlockingFlow(d, p);
203 markAsAuthenticated(d, p);
204 } else {
205 installBlockingFlow(d, p);
206 markAsBlocked(d, p);
207 }
208 log.info("Configuring port {}/{} as {}", d, p,
209 alreadyAuthenticated ? "AUTHENTICATED" : "BLOCKED");
210 }
211
212 private boolean notInMap(DeviceId deviceId, PortNumber portNumber) {
213 Map<PortNumber, BlockState> m = blockedPorts.get(deviceId);
214 return m == null || m.get(portNumber) == null;
215 }
216
217 private void logPortsNoLongerBlocked() {
218 for (Map.Entry<DeviceId, Map<PortNumber, BlockState>> entry :
219 oldMap.entrySet()) {
220 DeviceId d = entry.getKey();
221 Map<PortNumber, BlockState> portMap = entry.getValue();
222
223 for (PortNumber p : portMap.keySet()) {
224 if (notInMap(d, p)) {
225 clearBlockingFlow(d, p);
226 log.info("De-configuring port {}/{} (UNCHECKED)", d, p);
227 }
228 }
229 }
230 }
231
232
233 /**
234 * Reconfigures the port tracker using the supplied configuration.
235 *
236 * @param cfg the new configuration
237 */
238 void configurePortBlocking(BlockedPortsConfig cfg) {
239 // remember the old map; prepare a new map
240 oldMap = blockedPorts;
241 blockedPorts = new HashMap<>();
242
243 // for each configured device, add configured ports to map
244 for (String devId : cfg.deviceIds()) {
245 cfg.portIterator(devId)
246 .forEachRemaining(p -> configurePort(deviceId(devId),
247 portNumber(p)));
248 }
249
250 // have we de-configured any ports?
251 logPortsNoLongerBlocked();
252
253 // allow old map to be garbage collected
254 oldMap = null;
255 }
256
257 private List<PortAuthState> reportPortsAuthState() {
258 List<PortAuthState> result = new ArrayList<>();
259
260 for (Map.Entry<DeviceId, Map<PortNumber, BlockState>> entry :
261 blockedPorts.entrySet()) {
262 DeviceId d = entry.getKey();
263 Map<PortNumber, BlockState> portMap = entry.getValue();
264
265 for (PortNumber p : portMap.keySet()) {
266 result.add(new PortAuthState(d, p, portMap.get(p)));
267 }
268 }
269 Collections.sort(result);
270 return result;
271 }
272
273 /**
274 * Installs a "blocking" flow for device/port specified.
275 *
276 * @param d device id
277 * @param p port number
278 */
279 void installBlockingFlow(DeviceId d, PortNumber p) {
280 log.debug("Installing Blocking Flow at {}/{}", d, p);
281 // TODO: invoke SegmentRoutingService.block(...) appropriately
282 log.info("TODO >> Installing Blocking Flow at {}/{}", d, p);
283 }
284
285 /**
286 * Removes the "blocking" flow from device/port specified.
287 *
288 * @param d device id
289 * @param p port number
290 */
291 void clearBlockingFlow(DeviceId d, PortNumber p) {
292 log.debug("Clearing Blocking Flow from {}/{}", d, p);
293 // TODO: invoke SegmentRoutingService.block(...) appropriately
294 log.info("TODO >> Clearing Blocking Flow from {}/{}", d, p);
295 }
296
297
298 /**
299 * Designates the state of a given port. One of:
300 * <ul>
301 * <li> UNCHECKED: not configured for blocking </li>
302 * <li> BLOCKED: configured for blocking, and not yet authenticated </li>
303 * <li> AUTHENTICATED: configured for blocking, but authenticated </li>
304 * </ul>
305 */
306 public enum BlockState {
307 UNCHECKED,
308 BLOCKED,
309 AUTHENTICATED
310 }
311
312 /**
313 * A simple DTO binding of device identifier, port number, and block state.
314 */
315 public static final class PortAuthState implements Comparable<PortAuthState> {
316 private final DeviceId d;
317 private final PortNumber p;
318 private final BlockState s;
319
320 private PortAuthState(DeviceId d, PortNumber p, BlockState s) {
321 this.d = d;
322 this.p = p;
323 this.s = s;
324 }
325
326 @Override
327 public String toString() {
328 return String.valueOf(d) + "/" + p + " -- " + s;
329 }
330
331 @Override
332 public int compareTo(PortAuthState o) {
333 // NOTE: only compare against "deviceid/port"
334 int result = Comparators.ELEMENT_ID_COMPARATOR.compare(d, o.d);
335 return (result != 0) ? result : Long.signum(p.toLong() - o.p.toLong());
336 }
Ray Milkey1567b532018-02-05 10:25:32 -0800337
338 @Override
339 public boolean equals(Object obj) {
340 if (obj == null) {
341 return false;
342 }
343 if (getClass() != obj.getClass()) {
344 return false;
345 }
346 final PortAuthTracker.PortAuthState that = (PortAuthTracker.PortAuthState) obj;
347
348 return Comparators.ELEMENT_ID_COMPARATOR.compare(this.d, that.d) == 0 &&
349 p.toLong() == that.p.toLong();
350 }
351
352 @Override
353 public int hashCode() {
354 return Objects.hashCode(this.d, this.p);
355 }
Simon Hunt5789e1c2017-11-08 15:34:07 -0800356 }
357}