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